setSelectedFields() method in filter cmpt to keep custom persistence with visible fields; distinct titles assistenz/betreuer page; fix select logic to respect the possibility of dataset being filtered; beurteilungsnotiz required if note of that termin is negative; multiselect filter on qgate status columns + persistence of those; WIP switching mode to flat table showing all abgabetermine of all projektarbeiten to multi edit/delete/whatever with them selected

This commit is contained in:
Johann Hoffmann
2026-04-28 17:27:19 +02:00
parent c62c3c23f6
commit f51d0f72c6
6 changed files with 577 additions and 18 deletions
@@ -324,4 +324,9 @@
display: inline-block !important;
width: 100%;
}
}
/*conditional tooltips fix*/
.p-tooltip.custom-tooltip {
z-index: 8001 !important;
}
@@ -76,6 +76,9 @@ export const AbgabeMitarbeiterDetail = {
}
},
methods: {
terminIsInvalid(termin) {
return termin.note?.positiv == false && !termin.beurteilungsnotiz
},
getNoteBezeichnung(termin){
if(termin.noteBackend?.bezeichnung) {
return termin.noteBackend?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
@@ -556,6 +559,12 @@ export const AbgabeMitarbeiterDetail = {
class: "custom-tooltip"
}
},
getTooltipBeurteilungsnotiz() {
return {
value: this.$p.t('abgabetool/c4beurteilungsnotizBeiNegNote'),
class: "custom-tooltip"
}
},
getProjektarbeitTitel() {
if(this.projektarbeit?.titel) return this.$capitalize(this.$p.t('abgabetool/c4titel')) + ': ' + this.projektarbeit.titel
@@ -848,8 +857,10 @@ export const AbgabeMitarbeiterDetail = {
</div>
<div class="row mt-2" v-if="termin.bezeichnung?.benotbar">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" class="w-100" :disabled="!termin.allowedToSave"></Textarea>
<div class="col-12 col-md-9" v-tooltip.right="terminIsInvalid(termin) && getTooltipBeurteilungsnotiz">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz"
:class="{ 'p-invalid': terminIsInvalid(termin) }"
rows="1" class="w-100" :disabled="!termin.allowedToSave"></Textarea>
</div>
</div>
@@ -905,7 +916,7 @@ export const AbgabeMitarbeiterDetail = {
<div class="col-12 col-md-9">
<div class="row">
<div class="col-auto">
<button v-if="termin.allowedToSave" style="max-height: 40px;" class="btn btn-primary border-0" @click="saveTermin(termin)">
<button v-if="termin.allowedToSave && !terminIsInvalid(termin)" style="max-height: 40px;" class="btn btn-primary border-0" @click="saveTermin(termin)">
{{ $capitalize( $p.t('abgabetool/c4save') )}}
<i class="fa-solid fa-floppy-disk"></i>
</button>
@@ -53,9 +53,16 @@ export const AbgabetoolAssistenz = {
},
data() {
return {
mode: 'perProjectView',
qgate1FilterSelected: [],
qgate2FilterSelected: [],
count: 0,
filteredcount: 0,
selectedcount: 0,
countFlat: 0,
filteredcountFlat: 0,
selectedcountFlat: 0,
filteredRows: null,
studiensemesterOptions: null,
allSem: null,
curSem: null,
@@ -97,7 +104,9 @@ export const AbgabetoolAssistenz = {
}),
showAll: false,
tabulatorUuid: Vue.ref(0),
tabulatorUuidFlat: Vue.ref(0),
selectedData: [],
selectedDataFlat: [],
domain: '',
student_uid: null,
detail: null,
@@ -106,6 +115,8 @@ export const AbgabetoolAssistenz = {
selectedProjektarbeit: null,
tableBuiltResolve: null,
tableBuiltPromise: null,
tableBuiltResolveFlat: null,
tableBuiltPromiseFlat: null,
abgabeTableOptions: {
minHeight: 250,
index: 'projektarbeit_id',
@@ -221,8 +232,9 @@ export const AbgabetoolAssistenz = {
tooltip: this.toolTipFuncNextTermin,
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))),
headerFilter: 'list',
headerFilterParams: { valuesLookup: this.getQGateStatusList },
headerFilter: this.qgateHeaderFilterEditor,
headerFilterFunc: this.qgateHeaderFilterFunc,
headerFilterParams: {},
field: 'qgate1Status',
formatter: this.centeredTextFormatter,
titleFormatter: this.shortLongTitleFormatter,
@@ -236,8 +248,9 @@ export const AbgabetoolAssistenz = {
}
},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate2Status'))),
headerFilter: 'list',
headerFilterParams: { valuesLookup: this.getQGateStatusList },
headerFilter: this.qgateHeaderFilterEditor,
headerFilterFunc: this.qgateHeaderFilterFunc,
headerFilterParams: {},
field: 'qgate2Status',
formatter: this.centeredTextFormatter,
titleFormatter: this.shortLongTitleFormatter,
@@ -273,11 +286,299 @@ export const AbgabetoolAssistenz = {
},
{
event: 'dataFiltered',
handler: (filters, rows) => this.filteredcount = rows.length
handler: (filters, rows) => {
this.filteredRows = rows
this.filteredcount = rows.length
}
}
]};
],
abgabeTableOptionsFlat: {
minHeight: 250,
index: 'projektarbeit_id',
layout: 'fitData',
placeholder: Vue.computed(() => this.$capitalize(this.$p.t('global/noDataAvailable'))),
selectable: true,
rowHeight: 40,
renderVerticalBuffer: 2000,
columns: [
{
formatter: function (cell, formatterParams, onRendered) {
// create the built-in checkbox
if(!cell.getRow().getData().selectable) return
let checkbox = document.createElement("input");
checkbox.type = "checkbox";
// Handle select manually
checkbox.addEventListener("click", (e) => {
e.stopPropagation();
// call our function
if (formatterParams && formatterParams.handleClick) {
formatterParams.handleClick(e, cell);
}
});
cell.getRow().getData().checkbox = checkbox
let wrapper = document.createElement("div");
wrapper.style.cssText = "display: flex; justify-content: center; align-items: center; height: 100%; width: 100%;";
wrapper.appendChild(checkbox);
return wrapper;
},
titleFormatter: function (cell, formatterParams, onRendered) {
// create the built-in checkbox
let checkbox = document.createElement("input");
checkbox.type = "checkbox";
// Handle "select all" manually
checkbox.addEventListener("click", (e) => {
e.stopPropagation();
// call our function
if (formatterParams && formatterParams.handleClick) {
formatterParams.handleClick(e, cell);
}
});
return checkbox;
},
hozAlign: "center",
headerSort: false,
formatterParams: {
handleClick: this.selectHandlerFlat
},
titleFormatterParams: {
handleClick: this.selectAllHandlerFlat
},
width: 50,
cssClass: 'sticky-col'
},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, minWidth: 140, tooltip: false, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, minWidth: 100, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4studstatus'))), field: 'studienstatus', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1, minWidth: 150, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4orgformv2'))), field: 'orgform', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1, minWidth: 50, visible: false},
// --- termin data ---
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabetyp'))),
field: 'paabgabetyp_kurzbz',
headerFilter: true,
formatter: this.centeredTextFormatter,
minWidth: 120, widthGrow: 1
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabekurzbzv2'))),
field: 'kurzbz',
headerFilter: true,
formatter: this.centeredTextFormatter,
minWidth: 120, widthGrow: 1
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zieldatumv2'))),
field: 'datum',
headerFilter: dateFilter,
headerFilterFunc: this.headerFilterTerminCol,
sorter: (a, b) => new Date(a) - new Date(b),
formatter: (cell) => this.formatDate(cell.getValue()),
minWidth: 120, widthGrow: 1
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabedatum'))),
field: 'abgabedatum',
headerFilter: dateFilter,
headerFilterFunc: this.headerFilterTerminCol,
sorter: (a, b) => new Date(a) - new Date(b),
formatter: (cell) => this.formatDate(cell.getValue()),
minWidth: 120, widthGrow: 1
},
// --- status ---
{
title: '',
field: 'dateStyle',
headerSort: false,
formatter: (cell) => this.abgabterminFormatter(cell, true), // icon only mode
width: 48,
tooltip: (e, cell) => this.mapDateStyleToTabulatorTooltip(cell.getValue())
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))),
field: 'note',
formatter: (cell) => {
const val = cell.getValue();
if (!val) return '';
return val?.bezeichnung ?? this.notenOptions?.find(n => n.note == val)?.bezeichnung ?? val;
},
minWidth: 100, widthGrow: 1
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4notizQualGatev2'))),
field: 'beurteilungsnotiz',
headerFilter: true,
formatter: this.centeredTextFormatter,
minWidth: 150, widthGrow: 2, visible: false
},
// --- flags ---
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4fixterminv4'))),
field: 'fixtermin',
hozAlign: 'center',
formatter: 'tickCross',
width: 80,
headerFilter: 'tickCross',
headerFilterParams: { tristate: true },
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4upload_allowed'))),
field: 'upload_allowed',
hozAlign: 'center',
formatter: 'tickCross',
width: 80,
headerFilter: 'tickCross',
headerFilterParams: { tristate: true },
},
],
persistence: false,
persistenceID: "abgabetool_2026_03_16"
},
abgabeTableEventHandlersFlat: [
{
event: "rowSelectionChanged",
handler: async(data) =>
{
this.selectedDataFlat.filter(sd => !data.includes(sd)).forEach(fsd => {
if(fsd.checkbox) fsd.checkbox.checked = false
})
data.forEach(d => {
if(d.checkbox) d.checkbox.checked = true
})
this.selectedDataFlat = data
this.selectedcountFlat = data.length;
}
},
{
event: 'dataFiltered',
handler: (filters, rows) => {
this.filteredRowsFlat = rows
this.filteredcountFlat = rows.length
}
}
]
};
},
methods: {
async switchMode() {
if(this.mode == 'perProjectView') {
this.mode = 'flatView'
await this.tableBuiltPromiseFlat;
this.$refs.abgabeTableFlat.tabulator.setData(this.getAllTermine);
} else {
this.mode = 'perProjectView'
}
},
qgateHeaderFilterEditor(cell, onRendered, success, cancel, editorParams) {
const options = [
{ label: '[+] ' + this.$p.t('abgabetool/c4positivBenotet'), value: 'positive' },
{ label: '[-] ' + this.$p.t('abgabetool/c4negativBenotet'), value: 'negative' },
{ label: '[~] ' + this.$p.t('abgabetool/c4notYetGraded'), value: 'not_graded' },
{ label: '[?] ' + this.$p.t('abgabetool/c4notSubmitted'), value: 'not_submitted' },
{ label: '[o] ' + this.$p.t('abgabetool/c4notHappenedYet'), value: 'not_happened' },
{ label: '[--] ' + this.$p.t('abgabetool/c4keinTerminVorhanden'), value: 'no_termin' },
];
const field = cell.getField();
const stateKey = field === 'qgate1Status' ? 'qgate1FilterSelected' : 'qgate2FilterSelected';
let selected = [...(this[stateKey] || [])]; // restore persistence state
const wrapper = document.createElement('div');
wrapper.style.cssText = 'position: relative; width: 100%;';
const display = document.createElement('input');
display.readOnly = true;
display.placeholder = '';
display.style.cssText = 'padding: 4px; width: 100%; box-sizing: border-box; cursor: default; border: 1px solid; outline: none; background: #fff; appearance: none; caret-color: transparent;';
const dropdown = document.createElement('div');
dropdown.style.cssText = 'display: none; position: fixed; background: #fff; border: 1px solid; z-index: 9999; min-width: 180px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);';
options.forEach(opt => {
const row = document.createElement('label');
row.style.cssText = 'display: flex; align-items: center; gap: 6px; padding: 4px 8px; cursor: pointer; white-space: nowrap;';
row.addEventListener('mousedown', e => e.preventDefault());
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.value = opt.value;
cb.checked = selected.includes(opt.value); // sync with persistence
cb.addEventListener('change', () => {
if (cb.checked) {
selected.push(opt.value);
} else {
selected = selected.filter(v => v !== opt.value);
}
this[stateKey] = [...selected]; // sync with persistence
display.value = options.filter(o => selected.includes(o.value)).map(o => o.label).join(', ');
success([...selected]);
});
row.appendChild(cb);
row.appendChild(document.createTextNode(opt.label));
dropdown.appendChild(row);
});
display.value = options.filter(o => selected.includes(o.value)).map(o => o.label).join(', ');
display.addEventListener('click', () => {
if (dropdown.style.display === 'none') {
const rect = display.getBoundingClientRect();
dropdown.style.top = rect.bottom + 'px';
dropdown.style.left = rect.left + 'px';
dropdown.style.display = 'block';
} else {
dropdown.style.display = 'none';
}
});
display.addEventListener('blur', () => {
setTimeout(() => { dropdown.style.display = 'none'; }, 150);
});
document.body.appendChild(dropdown);
wrapper.appendChild(display);
cell.getElement().addEventListener('remove', () => dropdown.remove());
onRendered(() => display.focus());
return wrapper;
},
qgateHeaderFilterFunc(filterVal, rowVal, rowData, filterParams) {
if (!filterVal || !filterVal.length) return true;
const matches = (val) => {
switch (val) {
case 'positive': return rowVal === this.$p.t('abgabetool/c4positivBenotet');
case 'negative': return rowVal === this.$p.t('abgabetool/c4negativBenotet');
case 'not_graded': return rowVal === this.$p.t('abgabetool/c4notYetGraded');
case 'not_submitted':return rowVal === this.$p.t('abgabetool/c4notSubmitted');
case 'not_happened': return rowVal === this.$p.t('abgabetool/c4notHappenedYet');
case 'no_termin': return rowVal === this.$p.t('abgabetool/c4keinTerminVorhanden');
default: return true;
}
};
// OR logic — row passes if it matches any selected filter
return filterVal.some(val => matches(val));
},
redrawTableScrollSave() {
const table = this.$refs.abgabeTable.tabulator;
const scrollX = table.rowManager.scrollLeft;
@@ -451,11 +752,11 @@ export const AbgabetoolAssistenz = {
},
selectAllHandler(e, cell) {
const table = cell.getTable();
const rows = table.getRows();
const rows = this.filteredRows ?? table.getRows();
// custom select all logic
const allowed = rows.filter(r => r.getData().selectable);
const selected = allowed.every(r => r.isSelected());
const selected = table.getRows().every(r => r.isSelected());
if(selected){
allowed.forEach(r => r.deselect());
@@ -714,6 +1015,8 @@ export const AbgabetoolAssistenz = {
if (saved?.headerFilters && !this.headerFiltersRestored) {
this.headerFiltersRestored = true // instantly avoid retriggers
for (let hf of saved.headerFilters) {
if (hf.field === 'qgate1Status') this.qgate1FilterSelected = hf.value || [];
if (hf.field === 'qgate2Status') this.qgate2FilterSelected = hf.value || [];
table.setHeaderFilterValue(hf.field, hf.value);
}
}
@@ -734,6 +1037,99 @@ export const AbgabetoolAssistenz = {
}, 100);
}
this.stateRestored = true
// ensure that the filterCollapseables thingy has the correct values
this.$refs.abgabeTable.setSelectedFields();
}
});
},
handleTableBuiltFlat() {
const table = this.$refs.abgabeTableFlat.tabulator
this.tableBuiltResolveFlat()
table.on("columnMoved", () => {
this.saveState(table);
});
table.on("columnResized", () => {
this.saveState(table);
});
table.on("columnVisibilityChanged", () => {
this.saveState(table);
});
table.on("filterChanged", () => {
this.saveState(table);
});
table.on("headerFilterChanged", () => {
this.saveState(table);
});
table.on("dataSorted", () => {
this.saveState(table);
});
table.on("columnSorted", () => {
this.saveState(table);
});
table.on("sortersChanged", () => {
this.saveState(table);
});
const saved = this.loadState();
table.on("renderComplete", () => {
if(!this.stateRestored) {
if (saved?.columns && !this.colLayoutRestored) {
const layout = saved.columns.map(col => ({
field: col.field,
width: col.width,
visible: col.visible,
// add more if needed, but keep it simple
}));
table.setColumnLayout(layout);
this.colLayoutRestored = true;
}
if (saved?.filters && !this.filtersRestored) {
this.filtersRestored = true // instantly avoid retriggers
table.setFilter(saved.filters);
}
if (saved?.headerFilters && !this.headerFiltersRestored) {
this.headerFiltersRestored = true // instantly avoid retriggers
for (let hf of saved.headerFilters) {
table.setHeaderFilterValue(hf.field, hf.value);
}
}
if (saved?.sort?.length && !this.sortRestored) {
this.sortRestored = true;
setTimeout(() => {
const sortList = saved.sort.map(s => {
const col = table.columnManager.findColumn(s.field);
if (!col) {
return null;
}
return { column: col, dir: s.dir };
}).filter(Boolean);
table.setSort(sortList);
}, 100);
}
this.stateRestored = true
// ensure that the filterCollapseables thingy has the correct values
this.$refs.abgabeTableFlat.setSelectedFields();
}
@@ -768,11 +1164,11 @@ export const AbgabetoolAssistenz = {
},
formAction(cell) {
const actionButtons = document.createElement('div');
actionButtons.className = "d-flex gap-3"; // you can keep Bootstrap gap if loaded
actionButtons.className = "d-flex gap-3";
actionButtons.style.display = "flex";
actionButtons.style.alignItems = "stretch"; // buttons stretch to full height
actionButtons.style.alignItems = "stretch";
actionButtons.style.justifyContent = "start";
actionButtons.style.height = "100%"; // full grid cell height
actionButtons.style.height = "100%";
const val = cell.getValue();
@@ -1095,6 +1491,9 @@ export const AbgabetoolAssistenz = {
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
tableResolveFlat(resolve) {
this.tableBuiltResolveFlat = resolve
},
buildMailToLink(projekt) {
return 'mailto:' + projekt.student_uid +'@'+ this.domain
},
@@ -1148,6 +1547,9 @@ export const AbgabetoolAssistenz = {
handleUuidDefined(uuid) {
this.tabulatorUuid = uuid
},
handleUuidDefinedFlat(uuid) {
this.tabulatorUuidFlat = uuid
},
calcMaxTableHeight() {
const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
const tableDataSet = document.getElementById('filterTableDataset' + tableID);
@@ -1159,6 +1561,7 @@ export const AbgabetoolAssistenz = {
},
async setupMounted() {
this.tableBuiltPromise = new Promise(this.tableResolve)
this.tableBuiltPromiseFlat = new Promise(this.tableResolveFlat);
await this.tableBuiltPromise
await this.allConfigPromise
@@ -1173,6 +1576,31 @@ export const AbgabetoolAssistenz = {
},
},
computed: {
getAllTermine() {
if (!this.projektarbeiten) return [];
return this.projektarbeiten.flatMap(pa =>
pa.abgabetermine.map(termin => ({
...termin,
student_uid: pa.student_uid,
student_vorname: pa.student_vorname,
student_nachname: pa.student_nachname,
titel: pa.titel,
projektarbeit_id: pa.projektarbeit_id,
stg: pa.stg,
}))
);
},
countsToHTMLFlat() {
return this.$p.t('global/ausgewaehlt')
+ ': <strong>' + (this.selectedcountFlat || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gefiltert')
+ ': '
+ '<strong>' + (this.filteredcountFlat || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gesamt')
+ ': <strong>' + (this.countFlat || 0) + '</strong>';
},
countsToHTML() {
return this.$p.t('global/ausgewaehlt')
+ ': <strong>' + (this.selectedcount || 0) + '</strong>'
@@ -1561,7 +1989,7 @@ export const AbgabetoolAssistenz = {
<div id="abgabetable" style="max-height:40vw;">
<div class="row">
<div class="col-auto me-auto">
<h2 tabindex="1">{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<h2 tabindex="1">{{$p.t('abgabetool/abgabetoolTitleAdmin')}}</h2>
</div>
<div class="col-auto">
<label class="col-form-label">{{$capitalize($p.t('lehre/studiengang'))}}:</label>
@@ -1599,8 +2027,9 @@ export const AbgabetoolAssistenz = {
</div>
</div>
<hr>
<div :style="mode === 'perProjectView' ? '' : 'visibility: hidden; height: 0; overflow: hidden;'">
<core-filter-cmpt
:title="''"
:title="''"
@uuidDefined="handleUuidDefined"
ref="abgabeTable"
:description="countsToHTML"
@@ -1639,8 +2068,58 @@ export const AbgabetoolAssistenz = {
<i class="fa fa-envelope"></i>
</button>
<tiered-menu ref="menu" :model="emailItems" popup :autoZIndex="false" />
<button @click="switchMode">
switch mode
</button>
</template>
</core-filter-cmpt>
</div>
<div :style="mode === 'flatView' ? '' : 'visibility: hidden; height: 0; overflow: hidden;'">
<core-filter-cmpt
:title="''"
@uuidDefined="handleUuidDefinedFlat"
ref="abgabeTableFlat"
:description="countsToHTMLFlat"
:tabulator-options="abgabeTableOptionsFlat"
:tabulator-events="abgabeTableEventHandlersFlat"
@tableBuilt="handleTableBuiltFlat"
tableOnly
:sideMenu="false"
:useSelectionSpan="false"
>
<template #actions>
<Dropdown
@change="semesterChanged"
:placeholder="$capitalize($p.t('lehre/studiensemester'))"
:style="{'scroll-behavior': 'auto !important'}"
:optionLabel="getOptionLabelStudiensemester"
v-model="curSem"
:options="studiensemesterOptions"
>
<template #optionsgroup="slotProps">
<div>{{ option.studiensemester_kurzbz }}</div>
</template>
</Dropdown>
<button
v-if="emailItems.length"
role="button"
@click="evt => $refs.menu.toggle(evt)"
class="btn btn-outline-secondary dropdown-toggle"
aria-haspopup="true"
>
<i class="fa fa-envelope"></i>
</button>
<tiered-menu ref="menu" :model="emailItems" popup :autoZIndex="false" />
<button @click="switchMode">
switch mode
</button>
</template>
</core-filter-cmpt>
</div>
</div>
</template>
`,
@@ -1110,7 +1110,7 @@ export const AbgabetoolMitarbeiter = {
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div id="abgabetable" style="max-height:40vw;">
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<h2>{{$p.t('abgabetool/abgabetoolTitleBetreuer')}}</h2>
<hr>
<core-filter-cmpt
:title="''"
+5 -1
View File
@@ -220,6 +220,10 @@ export const CoreFilterCmpt = {
else
this.getFilter();
},
setSelectedFields() {
const cols = this.tabulator.getColumns();
this.selectedFields = cols.filter(col => col.isVisible()).map(col => col.getField());
},
async initTabulator() {
let placeholder = '< Phrasen Plugin not loaded! >';
if (this.$p) {
@@ -337,7 +341,7 @@ export const CoreFilterCmpt = {
this.tabulator.on('tableBuilt', () => {
const cols = this.tabulator.getColumns();
this.fields = cols.map(col => col.getField());
this.selectedFields = cols.filter(col => col.isVisible()).map(col => col.getField());
this.setSelectedFields();
if (this.tabulator.options.persistence.headerFilter)
this._setHeaderFilter();
});
+60
View File
@@ -43544,6 +43544,46 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'abgabetoolTitleBetreuer',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Bachelor-/Masterarbeiten Betreuungen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Supervision of Bachelor's/Master's theses",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'abgabetoolTitleAdmin',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Bachelor-/Masterarbeiten Administration",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Administration of Bachelor's/Master's theses",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
@@ -44929,6 +44969,26 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4beurteilungsnotizBeiNegNote',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Wenn Sie eine negative Note erteilen, müssen Sie eine Beurteilungsnotiz ausfüllen!",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'If you give a failing grade, you must fill out an evaluation form!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',