Merge branch 'feature-63445/Studierendenverwaltung_Filter' into studvw_2025-11_rc

This commit is contained in:
Harald Bamberger
2025-11-04 17:10:24 +01:00
8 changed files with 755 additions and 108 deletions
@@ -1,5 +1,6 @@
import {CoreFilterCmpt} from "../../filter/Filter.js";
import ListNew from './List/New.js';
import ListFilter from './List/Filter.js';
import draggable from '../../../directives/draggable.js';
@@ -7,7 +8,8 @@ export default {
name: "ListPrestudents",
components: {
CoreFilterCmpt,
ListNew
ListNew,
ListFilter
},
directives: {
draggable
@@ -131,7 +133,7 @@ export default {
{
return Promise.resolve({ data: []});
}
return this.$api.call({url, params});
return this.$api.call({method: 'post', url, params});
},
ajaxResponse: (url, params, response) => {
return response?.data;
@@ -169,8 +171,7 @@ export default {
],
focusObj: null, // TODO(chris): this should be in the filter component
lastSelected: null,
filterKontoCount0: undefined,
filterKontoMissingCounter: undefined,
filter: [],
count: 0,
filteredcount: 0,
selectedcount: 0,
@@ -248,6 +249,10 @@ export default {
}
}
},
updateFilter(filter) {
this.filter = filter;
this.updateUrl();
},
updateUrl(endpoint, first) {
this.lastSelected = first ? undefined : this.selected;
@@ -268,14 +273,9 @@ export default {
encodeURIComponent(this.currentSemester)
);
const params = {}, filter = {};
if (this.filterKontoCount0)
filter.konto_count_0 = this.filterKontoCount0;
if (this.filterKontoMissingCounter)
filter.konto_missing_counter = this.filterKontoMissingCounter;
if (filter.konto_count_0 || filter.konto_missing_counter)
params.filter = filter;
const params = {};
if (this.filter.length)
params.filter = this.filter;
if (!this.$refs.table.tableBuilt) {
if (!this.$refs.table.tabulator) {
@@ -360,11 +360,11 @@ export default {
},
// TODO(chris): focusin, focusout, keydown and tabindex should be in the filter component
// TODO(chris): filter component column chooser has no accessibilty features
template: `
template: /* html */`
<div class="stv-list h-100 pt-3">
<div
class="tabulator-container d-flex flex-column h-100"
:class="{'has-filter': filterKontoCount0 || filterKontoMissingCounter}"
:class="{'has-filter': filter.length}"
tabindex="0"
@focusin="onFocus"
@keydown="onKeydown"
@@ -388,29 +388,7 @@ export default {
<template #filter>
<div class="card">
<div class="card-body">
<div class="input-group mb-3">
<label class="input-group-text col-4" for="stv-list-filter-konto-count-0">{{ $p.t('stv/konto_filter_count_0') }}</label>
<select class="form-select" id="stv-list-filter-konto-count-0" v-model="filterKontoCount0" @input="$nextTick(updateUrl)">
<option v-for="typ in lists.buchungstypen" :key="typ.buchungstyp_kurzbz" :value="typ.buchungstyp_kurzbz">
{{ typ.beschreibung }}
</option>
</select>
<button v-if="filterKontoCount0" class="btn btn-outline-secondary" @click="filterKontoCount0 = undefined; updateUrl()">
<i class="fa fa-times"></i>
</button>
</div>
<div class="input-group">
<label class="input-group-text col-4" for="stv-list-filter-konto-missing-counter">{{ $p.t('stv/konto_filter_missing_counter') }}</label>
<select class="form-select" id="stv-list-filter-konto-missing-counter" v-model="filterKontoMissingCounter" @input="$nextTick(updateUrl)">
<option value="alle">{{ $p.t('stv/konto_all_types') }}</option>
<option v-for="typ in lists.buchungstypen" :key="typ.buchungstyp_kurzbz" :value="typ.buchungstyp_kurzbz">
{{ typ.beschreibung }}
</option>
</select>
<button v-if="filterKontoMissingCounter" class="btn btn-outline-secondary" @click="filterKontoMissingCounter = undefined; updateUrl()">
<i class="fa fa-times"></i>
</button>
</div>
<list-filter @change="updateFilter" />
</div>
</div>
</template>
@@ -0,0 +1,102 @@
import FilterItem from './Filter/Item.js';
import ApiStvApp from '../../../../api/factory/stv/app.js';
export default {
name: "ListPrestudentsFilter",
components: {
FilterItem
},
emits: [
'change'
],
data() {
return {
filters: [],
filterConfig: [// TODO(chris): get from BE!
{
name: 'stv/konto_filter_count_0',
type: 'konto',
fixed: {
missing: true,
usestdsem: true
},
dynamic: {
buchungstyp_kurzbz: {
type: 'select',
values: {
test1: 'Test1',
test2: 'Test2'
}
}
}
},
{
name: 'stv/konto_filter_missing_counter',
type: 'konto_counter',
dynamic: {
buchungstyp_kurzbz: {
type: 'select',
values: {
test1: 'Test1',
test2: 'Test2'
}
},
samestg: {
type: 'bool',
label: 'stv/konto',
default: true
}
}
}
]
}
},
computed: {
cleanFilters() {
return this.filters.filter(filter => {
if (!filter.type)
return false;
if (Object.values(filter).some(v => v === undefined))
return false;
return true;
});
}
},
watch: {
cleanFilters(n) {
this.$emit('change', n);
}
},
methods: {
add() {
this.filters.push({});
},
remove(index) {
this.filters.splice(index, 1);
}
},
created() {
this.$api
.call(ApiStvApp.configFilter())
.then(result => {
this.filterConfig = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
},
template: /* html */`
<div class="stv-list-filter h-100">
<button class="btn btn-outline-dark" type="button" @click="add">
<span class="fa-solid fa-plus" aria-hidden="true"></span>
{{ $p.t('filter/filter') }}
</button>
<filter-item
v-for="(filter, i) in filters"
:key="i"
v-model="filters[i]"
:filter-config="filterConfig"
class="mt-3"
@remove="remove(i)"
/>
</div>`
};
@@ -0,0 +1,113 @@
export default {
name: "FilterItem",
props: {
modelValue: Object,
filterConfig: Array
},
emits: [
'update:modelValue',
'remove'
],
data() {
return {
//type: this.modelValue.type
};
},
computed: {
value: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
}
},
filterid: {
get() {
return this.modelValue.filterid
},
set(filterid) {
const config = this.filterConfig.find(config => config.id == filterid);
const dynamic = Object.fromEntries(
Object.keys(config.dynamic || {}).map(key => {
return [
key,
config.dynamic[key].default
];
})
);
this.value = {
filterid,
type: config.type,
...(config.fixed || {}),
...dynamic
};
}
},
currentConfig() {
return this.filterConfig.find(config => config.id == this.filterid);
}
},
methods: {
update() {
this.$emit('update:modelValue', this.value);
}
},
template: /* html */`
<div class="stv-list-filter-item input-group">
<label
class="input-group-text col-4"
for="stv-list-filter-konto-count-0"
>
{{ $p.t('stv/filter_for') }}
</label>
<select
v-model="filterid"
id="stv-list-filter-konto-count-0"
class="form-select"
>
<option
v-for="(filter, i) in filterConfig"
:key="i"
:value="filter.id"
>
{{ filter.label }}
</option>
</select>
<template v-for="(conf, key) in currentConfig?.dynamic" :key="key">
<select
v-if="conf.type == 'select'"
v-model="modelValue[key]"
class="form-select"
@input="update"
>
<option
v-for="(label, value) in conf.values"
:key="conf.value_key ? label[conf.value_key] : value"
:value="conf.value_key ? label[conf.value_key] : value"
>
{{ conf.label_key ? label[conf.label_key] : label }}
</option>
</select>
<template v-else-if="conf.type == 'bool'">
<div class="input-group-text">
<label class="form-check-label">
<input
v-model="modelValue[key]"
type="checkbox"
class="form-check-input me-1"
@input="update"
>
{{ conf.label }}
</label>
</div>
</template>
</template>
<button
class="btn btn-outline-secondary"
:title="$p.t('ui/entfernen')"
:aria-label="$p.t('ui/entfernen')"
@click="$emit('remove')"
><i class="fa fa-times"></i></button>
</div>`
};