diff --git a/public/js/TableWidget.js b/public/js/TableWidget.js index ec93422fc..4fa89aa0b 100644 --- a/public/js/TableWidget.js +++ b/public/js/TableWidget.js @@ -582,7 +582,16 @@ var FHC_TableWidget = { options.columns = arrayTabulatorColumns; options.data = data.dataset; - options.persistence = (typeof options.persistence == 'undefined') ? true : options.persistence; // enables persistence (default store in localStorage if available, else in cookie) + + let defaultPersistence = { + sort: true, + columns: true, + filter: false, + headerFilter: false, + group: false, + page: false, + } + options.persistence = (typeof options.persistence == 'undefined') ? defaultPersistence : options.persistence; // enables persistence (default store in localStorage if available, else in cookie) options.persistenceID = (typeof options.persistenceID == 'undefined') ? data.tableUniqueId : options.persistenceID; // persistenceID to store persistence data seperately for multiple tables options.movableColumns = (typeof options.movableColumns == 'undefined') ? true : options.movableColumns; // allows changing column order options.tooltipsHeader = (typeof options.tooltipsHeader == 'undefined') ? true : options.tooltipsHeader; // set header tooltip with column title diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index c6cb6cdb3..cd4f49dac 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -20,7 +20,6 @@ import FilterConfig from './Filter/Config.js'; import FilterColumns from './Filter/Columns.js'; import TableDownload from './Table/Download.js'; import collapseAutoClose from '../../directives/collapseAutoClose.js'; -import { defaultHeaderFilter } from '../../tabulator/filters/defaultHeaderFilter.js'; // const FILTER_COMPONENT_NEW_FILTER = 'Filter Component New Filter'; @@ -81,6 +80,7 @@ export const CoreFilterCmpt = { uuid: 0, // FilterCmpt properties filterName: null, + filterActive: false, fields: null, dataset: null, datasetMetadata: null, @@ -100,7 +100,15 @@ export const CoreFilterCmpt = { tabulator: null, tableBuilt: false, tabulatorHasSelector: false, - selectedData: [] + selectedData: [], + persistence: { + sort: true, + columns: true, + filter: false, + headerFilter: false, + group: false, + page: false, + } }; }, computed: { @@ -207,12 +215,11 @@ export const CoreFilterCmpt = { layout: "fitDataStretch", movableColumns: true, columnDefaults:{ - tooltip: true, - headerFilterFunc: defaultHeaderFilter, + tooltip: true }, placeholder, reactiveData: true, - persistence: true + persistence: this.persistence, }, ...(this.tabulatorOptions || {})}; if (!this.tableOnly) { @@ -278,11 +285,15 @@ export const CoreFilterCmpt = { const cols = this.tabulator.getColumns(); this.fields = cols.map(col => col.getField()); this.selectedFields = cols.filter(col => col.isVisible()).map(col => col.getField()); - + if (this.tabulator.options.persistence.headerFilter) + this._setHeaderFilter(); }); } + this.tabulator.on("dataFiltered", filters => { + this.filterActive = filters.length > 0; + }); }, updateTabulator() { if (this.tabulator) { @@ -296,6 +307,23 @@ export const CoreFilterCmpt = { this.tabulatorHasSelector = this.tabulatorOptions.selectable || this.filteredColumns.filter(el => el.formatter == 'rowSelection').length; this.tabulator.setColumns(this.filteredColumns); this.tabulator.setData(this.filteredData); + this._setHeaderFilter() + }, + clearFilters() + { + let existingFilters = this.tabulator.getHeaderFilters(); + existingFilters.forEach(filter => { + this.tabulator.setHeaderFilterValue(filter.field, ""); + }); + this.tabulator.clearFilter(); + this.filterActive = false; + }, + _setHeaderFilter() + { + const existingFilters = this.tabulator.getHeaderFilters(); + existingFilters.forEach(filter => { + this.tabulator.setHeaderFilterValue(filter.field, filter.value); + }); }, /** * @@ -578,16 +606,21 @@ export const CoreFilterCmpt = { alert('"nwNewEntry" listener is mandatory when sideMenu is true'); this.uuid = _uuid++; this.$emit('uuidDefined', this.uuid) - if (!this.tableOnly) - this.getFilter(); // get the filter data }, mounted() { - this.initTabulator(); + + this.initTabulator().then(() => { + if (!this.tableOnly) { + this.selectedFilter = window.location.hash ? window.location.hash.slice(1) : null; + this.getFilter(); // get the filter data + } + }); + }, template: ` + + + diff --git a/public/js/tabulator/filters/defaultHeaderFilter.js b/public/js/tabulator/filters/defaultHeaderFilter.js deleted file mode 100644 index 7e463921b..000000000 --- a/public/js/tabulator/filters/defaultHeaderFilter.js +++ /dev/null @@ -1,82 +0,0 @@ -function parseFilterExpression(expression){ - const includeGroups = []; - const excludeTerms = []; - const comparisons = []; - - const andParts = expression.split('&&').map(part => part.trim()); - - andParts.forEach(part => { - const orTerms = part.split('||').map(p => p.trim()); - const orRegexes = []; - - orTerms.forEach(term => { - - const comparisonMatch = term.match(/^(<=|>=|<|>|=|!=)\s*(\d+)$/); - - if (comparisonMatch) - { - const operator = comparisonMatch[1]; - const number = parseFloat(comparisonMatch[2]); - - comparisons.push({ operator, number }); - } - else if (term.startsWith('!')) - { - const excludeTerm = term.substring(1).trim().replace(/\*/g, '.*'); - excludeTerms.push(new RegExp(excludeTerm, 'i')); - } - else - { - const includeTerm = term.replace(/\*/g, '.*'); - orRegexes.push(new RegExp(includeTerm, 'i')); - } - }); - - if (orRegexes.length > 0) - { - includeGroups.push(orRegexes); - } - }); - - return { includeGroups, excludeTerms, comparisons }; -} - -export function defaultHeaderFilter(headerValue, rowValue) -{ - const { includeGroups, excludeTerms, comparisons } = parseFilterExpression(headerValue); - - const includes = includeGroups.every(group => - group.some(regex => regex.test(rowValue)) - ); - - const excludes = excludeTerms.every(regex => !regex.test(rowValue)); - - const comparisonCheck = comparisons.every(({ operator, number }) => { - let value = rowValue; - - if (!isNaN(number)) - { - value = parseFloat(rowValue); - if (isNaN(value)) return false; - } - - switch (operator) { - case '<': - return value < number; - case '>': - return value > number; - case '<=': - return value <= number; - case '>=': - return value >= number; - case '=': - return value === number; - case '!=': - return value !== number; - default: - return false; - } - }); - - return includes && excludes && comparisonCheck; -} diff --git a/public/js/tabulator/filters/extendedHeaderFilter.js b/public/js/tabulator/filters/extendedHeaderFilter.js new file mode 100644 index 000000000..d8ead88bc --- /dev/null +++ b/public/js/tabulator/filters/extendedHeaderFilter.js @@ -0,0 +1,125 @@ +function parseFilterExpression(expression) +{ + const collections = []; + + try { + const orParts = expression.split('||').map(part => part.trim()); + + orParts.forEach(part => { + + const andParts = part.split('&&').map(p => p.trim()); + + const collection = { positives: [], negatives: [] }; + + andParts.forEach(term => { + + const comparisonMatch = term.match(/^(<=|>=|<|>|=|!=)\s*(\d+(?:[.,]\d+)?)$/); + + if (comparisonMatch) + { + const operator = comparisonMatch[1]; + const numberStr = comparisonMatch[2].replace(',', '.'); + const number = parseFloat(numberStr); + collection.positives.push({ type: 'comparison', operator, number }); + } + else if (term.startsWith('!')) + { + const excludeTerm = term.substring(1).trim().replace(/\*/g, '.*'); + collection.negatives.push({ type: 'regex', regex: new RegExp(excludeTerm, 'i') }); + } + else + { + const includeTerm = term.replace(/\*/g, '.*'); + collection.positives.push({ type: 'regex', regex: new RegExp(includeTerm, 'i') }); + } + }); + collections.push(collection); + }); + } catch (e) {} + return collections; +} + +export function extendedHeaderFilter(headerValue, rowValue) +{ + if (typeof headerValue === 'boolean') + { + return rowValue === headerValue; + } + + const collections = parseFilterExpression(headerValue); + + try { + + return collections.some(collection => { + + let positives = collection.positives.length === 0 || collection.positives.every(condition => { + + if (condition.type === 'comparison') + { + let value = parseFloat(rowValue); + if (isNaN(value)) return false; + + switch (condition.operator) { + case '<': + return value < condition.number; + case '>': + return value > condition.number; + case '<=': + return value <= condition.number; + case '>=': + return value >= condition.number; + case '=': + return value === condition.number; + case '!=': + return value !== condition.number; + default: + return false; + } + } + else if (condition.type === 'regex') + { + return condition.regex.test(rowValue); + } + return false; + }); + + let negatives = collection.negatives.every(condition => { + return !condition.regex.test(rowValue); + }); + + return positives && negatives; + }); + } catch (e) { + + } +} +export function tagHeaderFilter(headerValue, rowValue) { + + let data; + + try { + data = typeof rowValue === 'string' ? JSON.parse(rowValue) : rowValue; + } catch (error) { + return false; + } + + let combinedText; + + if (Array.isArray(data)) + { + combinedText = data + .map(item => `${item.beschreibung} ${item.notiz}`) + .join(' '); + } + else if (typeof data === 'object' && data !== null) + { + combinedText = `${data.beschreibung} ${data.notiz}`; + } + else + { + combinedText = String(data); + } + + return extendedHeaderFilter(headerValue, combinedText) +} + diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index cfaf85ec7..5ced0bbf6 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -1775,6 +1775,26 @@ $phrases = array( ) ) ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'filterdelete', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Filter löschen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Clear filter', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'anrechnung',