Merge branch 'pep_deploy_2025_03_06'

This commit is contained in:
Andreas Österreicher
2025-03-06 10:36:26 +01:00
17 changed files with 593 additions and 202 deletions
+10 -1
View File
@@ -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
+31 -12
View File
@@ -82,10 +82,10 @@ export default {
this.tagData.style = item.style;
this.tagData.zuordnung_typ = this.zuordnung_typ;
this.tagData.done = item.done;
this.tagData.insertamum = item.insertamum;
this.tagData.updateamum = item.updateamum;
this.tagData.updatevon = item.updatevon;
this.tagData.insertvon = item.insertvon;
this.tagData.insertamum = this.formatDateTime(item.insertamum)
this.tagData.updateamum = this.formatDateTime(item.updateamum)
this.tagData.bearbeiter = item.bearbeiter;
this.tagData.verfasser = item.verfasser;
if (item && item.notiz_id)
{
@@ -146,8 +146,6 @@ export default {
this.$refs.tagModal.hide();
});
}
},
async doneTag()
{
@@ -181,22 +179,35 @@ export default {
id: "",
done: false,
insertamum: "",
insertvon: "",
verfasser: "",
updateamum: "",
updatevon: "",
bearbeiter: "",
response: ""
};
this.selectedTagId = null;
this.mode = "create";
},
formatDateTime: (dateString) => {
if (!dateString) return null;
return new Date(dateString).toLocaleString('de-AT', {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
}
},
template: `
<div class="plus_button_container" @mouseleave="hideList">
<span :title="values.length === 0 ? 'Bitte Zeilen markieren' : ''">
<button @mouseover="showList = true"
:disabled="!values || values.length === 0"
class="btn btn-sm">
<i class="fa-solid fa-tag fa-xl"></i>
</button>
</span>
<ul v-if="showList" class="dropdown_list">
<li v-for="(item, index) in tags" :key="index" @click="openModal(item)" :title="item.bezeichnung">
{{ item.bezeichnung }}
@@ -221,7 +232,15 @@ export default {
field="notiz"
placeholder="Notiz..."
></form-input>
<div class="modificationdate">angelegt von {{ tagData.insertvon }} am {{ tagData.insertamum }}</div>
<div class="modificationdate">
<span v-if="tagData.verfasser">
{{ $p.t('notiz', 'tag_verfasser', { 0: tagData.verfasser, 1: tagData.insertamum }) }}
</span>
<br />
<span v-if="tagData.bearbeiter && tagData.insertamum !== tagData.updateamum">
{{ $p.t('notiz', 'tag_bearbeiter', { 0: tagData.bearbeiter, 1: tagData.updateamum }) }}
</span>
</div>
</div>
</template>
<template #footer>
@@ -232,12 +251,12 @@ export default {
class="btn btn-success me-2"
@click="doneTag"
>
{{ tagData.done ? 'Rückgängig' : 'Erledigt' }}
{{ tagData.done ? $p.t('notiz', 'tag_rueckgaengig') : $p.t('notiz', 'tag_erledigt') }}
</button>
<button v-if="mode === 'edit'" class="btn btn-danger" @click="deleteTag">Löschen</button>
<button v-if="mode === 'edit'" class="btn btn-danger" @click="deleteTag">{{ $p.t('global', 'loeschen' )}}</button>
</div>
<button type="button" class="btn btn-primary" @click="saveTag">
{{ mode === "edit" ? $p.t('ui', 'bearbeiten') : $p.t('studierendenantrag', 'btn_create') }}
{{ mode === "edit" ? $p.t('global', 'speichern') : $p.t('global', 'create') }}
</button>
</div>
</template>
+46 -10
View File
@@ -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';
import moduleLayoutFitDataStretchFrozen from '../../tabulator/layouts/fitDataStretchFrozen.js';
@@ -84,6 +83,7 @@ export const CoreFilterCmpt = {
uuid: 0,
// FilterCmpt properties
filterName: null,
filterActive: false,
fields: null,
dataset: null,
datasetMetadata: null,
@@ -103,7 +103,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: {
@@ -210,12 +218,11 @@ export const CoreFilterCmpt = {
layout: "fitDataStretchFrozen",
movableColumns: true,
columnDefaults:{
tooltip: true,
headerFilterFunc: defaultHeaderFilter,
tooltip: true
},
placeholder,
reactiveData: true,
persistence: true
persistence: this.persistence,
}, ...(this.tabulatorOptions || {})};
if (!this.tableOnly) {
@@ -281,11 +288,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) {
@@ -299,6 +310,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);
});
},
/**
*
@@ -581,16 +609,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: `
<!-- Load filter data -->
<core-fetch-cmpt
v-if="!tableOnly"
v-if="!tableOnly && fetchCmptApiFunction"
v-bind:api-function="fetchCmptApiFunction"
v-bind:api-function-parameters="fetchCmptApiFunctionParams"
v-bind:refresh="fetchCmptRefresh"
@@ -629,6 +662,9 @@ export const CoreFilterCmpt = {
<a v-if="!tableOnly || $slots.filter" href="#" class="btn btn-link px-0 text-dark" data-bs-toggle="collapse" :data-bs-target="'#collapseFilters' + idExtra">
<span class="fa-solid fa-xl fa-filter"></span>
</a>
<a v-if="filterActive" class="btn btn-link px-0 text-dark" :title="$p.t('ui','filterdelete')" @click="clearFilters">
<span class="fa-solid fa-xl fa-filter-circle-xmark"></span>
</a>
<a href="#" class="btn btn-link px-0 text-dark" data-bs-toggle="collapse" :data-bs-target="'#collapseColumns' + idExtra">
<span class="fa-solid fa-xl fa-table-columns"></span>
</a>
+39 -10
View File
@@ -7,26 +7,49 @@ Tabulator.extendModule('filter', 'filters', {
"dates": (headerValue, rowValue) => {
if (!headerValue)
return true;
let v = new Date(rowValue);
if (Array.isArray(headerValue)) {
if (headerValue[1]) {
return v >= headerValue[0] && v <= headerValue[1].setHours(23, 59, 59, 999);
let rowDate = new Date(rowValue);
if (Array.isArray(headerValue))
{
let startDate = new Date(headerValue[0]);
if (headerValue[1])
{
let endDate = new Date(headerValue[1]);
endDate.setHours(23, 59, 59, 999);
return rowDate >= startDate && rowDate <= endDate;
}
return v.toDateString() == headerValue[0].toDateString();
return rowDate.toDateString() === startDate.toDateString();
}
return v.toDateString() == headerValue.toDateString();
let singleDate = new Date(headerValue);
return rowDate.toDateString() === singleDate.toDateString();
}
});
function dateFilter(cell, onRendered, success) {
let div = document.createElement('div');
let initialValue = null;
let val = cell.getValue();
if (Array.isArray(val))
{
const start = val[0] ? new Date(val[0]) : null;
const end = val[1] ? new Date(val[1]) : null;
initialValue = [start, end];
}
Vue.createApp({
components: {
PrimevueCalendar: primevue.calendar
},
data() {
return {
val: null
val: initialValue
}
},
watch: {
@@ -34,10 +57,16 @@ function dateFilter(cell, onRendered, success) {
success(n);
}
},
template: `<primevue-calendar v-model="val" selection-mode="range" :manual-input="false" show-button-bar></primevue-calendar>`
template: `<primevue-calendar
v-model="val"
selection-mode="range"
:manual-input="false"
show-button-bar
:showIcon="true"
dateFormat="dd.mm.yy">
</primevue-calendar>`
}).use(primevue.config.default).mount(div);
return div;
}
export { dateFilter as 'dateFilter' };
export { dateFilter };
@@ -1,87 +0,0 @@
function parseFilterExpression(expression){
const includeGroups = [];
const excludeTerms = [];
const comparisons = [];
if( typeof expression !== 'string' ) {
comparisons.push({ operator: '=', number: expression });
return { includeGroups, excludeTerms, 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) && typeof number !== 'boolean')
{
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;
}
@@ -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)
}