mobility legende; TopCalc Row (sum, negative, prueflinge); fix row selection issues;

This commit is contained in:
Johann Hoffmann
2025-08-18 11:25:38 +02:00
parent ee4b61f549
commit 2f7fe05d21
3 changed files with 126 additions and 8 deletions
@@ -36,7 +36,8 @@ $includesArray = array(
'vendor/npm-asset/primevue/multiselect/multiselect.js'
),
'customJSModules' => array(
'public/js/apps/Dashboard/Fhc.js'
'public/js/apps/Dashboard/Fhc.js',
'vendor/olifolkerd/tabulator5/src/js/modules/ColumnCalcs/ColumnCalcs.js'
),
);
@@ -5,11 +5,14 @@ import ApiStudiensemester from "../../../api/factory/studiensemester.js";
import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import LehreinheitenModule from '../../DropdownModes/LehreinheitenModule';
import MobilityLegende from '../../Mobility/Legende.js';
export const Benotungstool = {
name: "Benotungstool",
components: {
BsModal,
CoreFilterCmpt,
MobilityLegende,
Dropdown: primevue.dropdown,
Password: primevue.password,
Textarea: primevue.textarea,
@@ -73,7 +76,7 @@ export const Benotungstool = {
{
event: "cellClick",
handler: async (e, cell) => {
console.log('cellClick Handler normal')
}
},
{
@@ -430,20 +433,42 @@ export const Benotungstool = {
return true; // student can be selected to add pruefung
},
rowFormatter: this.fixTabulatorSelectionFormatter,
columns: [
{
formatter: "rowSelection",
titleFormatter: "rowSelection", // Adds "select all" checkbox in header
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();
console.log("Custom select all handler");
// Or call your function
if (formatterParams && formatterParams.handleClick) {
formatterParams.handleClick(e, cell);
}
});
return checkbox;
},
hozAlign: "center",
headerSort: false,
titleFormatterParams: {
handleClick: this.selectAllHandler
},
cellClick: function (e, cell) {
cell.getRow().toggleSelect();
},
width: 50,
},
{title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('benotungstool/c4antrittCount')), field: 'hoechsterAntritt', tooltip: false, widthGrow: 1},
{title: 'UID', field: 'uid', tooltip: false, widthGrow: 1, topCalc:"sum"},
{title: 'UID', field: 'uid', tooltip: false, widthGrow: 1, topCalc: this.sumCalcFunc},
{title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1},
{title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnote', widthGrow: 1, formatter: this.teilnotenFormatter},
@@ -467,7 +492,7 @@ export const Benotungstool = {
if(!noteOption) return true
// also if student has any pruefungsnote disable noten selection
if(this.pruefungen.find(p => p.student_uid == rowData.uid)) return false
if(this.pruefungen?.find(p => p.student_uid == rowData.uid)) return false
return noteOption.lkt_ueberschreibbar
},
@@ -476,7 +501,7 @@ export const Benotungstool = {
const value = cell.getValue()
const match = this.notenOptions?.find(opt => opt.note == value)
const val = match ? match.bezeichnung : value
const p = this.pruefungen.find(p => p.student_uid == rowData.uid)
const p = this.pruefungen?.find(p => p.student_uid == rowData.uid)
let style = ''
if(val === undefined) return ''
@@ -497,17 +522,76 @@ export const Benotungstool = {
{title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')),
field: 'note',
formatter: this.notenFormatter,
topCalc: this.negativeNotenCalc,
topCalcFormatter: this.negativeNotenCalcFormatter,
headerFilter: 'list',
headerFilterParams: () => {
return { values: ["\u00A0", this.$p.t('benotungstool/c4noteEmpty'),this.$p.t('benotungstool/c4positiv'), this.$p.t('benotungstool/c4negativ') ,...this.notenOptions.map(opt => opt.bezeichnung)] }
},
headerFilterFunc: this.notenFilterFunc,
widthGrow: 1},
{title: Vue.computed(() => this.$p.t('benotungstool/c4kommPruef')), field: 'kommPruef', widthGrow: 1, formatter: this.pruefungFormatter, hozAlign:"center", minWidth: 150}
{title: Vue.computed(() => this.$p.t('benotungstool/c4kommPruef')),
field: 'kommPruef', widthGrow: 1,
formatter: this.pruefungFormatter,
topCalc: this.terminCalcFunc,
topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center", minWidth: 150}
],
persistence: false,
}
},
selectAllHandler(e, col) {
const table = col.getTable();
const rows = table.getRows();
// custom select all logic
const allowed = rows.filter(r => r.getData().selectable);
const selected = allowed.every(r => r.isSelected());
if(selected){
allowed.forEach(r => r.deselect());
} else {
allowed.forEach(r => r.select());
}
// stop Tabulators built-in handler
e.stopPropagation();
return false;
},
fixTabulatorSelectionFormatter(row) {
// if a row is not selectable, remove the checkbox from the dom
const data = row.getData()
const notSelectable = data.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef') || data.hoechsterAntritt >= 3
if(notSelectable) row.getElement().children[0]?.children[0]?.remove()
},
terminCalcFunc(entries) {
return entries.reduce((acc, cur) => {
if(cur !== undefined) acc++
return acc
}, 0)
},
terminCalcFormatter(cell) {
const cellval = cell.getValue()
// TODO: phrase
return 'Prüflinge: ' + cellval
},
negativeNotenCalcFormatter(cell) {
const cellval = cell.getValue()
// TODO: phrase
return 'Negativ: ' + cellval
},
negativeNotenCalc(entries) {
return entries.reduce((acc, cur) => {
const opt = this.notenOptions.find(opt => opt.note == cur)
if(opt && !opt.positiv) acc++
return acc
}, 0)
},
sumCalcFunc(entries) {
return entries.length
},
notenFilterFunc(filterVal, rowVal) {
// option of the searchterm
const opt = this.notenOptions.find(opt => opt.bezeichnung === filterVal)
@@ -885,6 +969,15 @@ export const Benotungstool = {
else s.teilnote += ('<span style="color: red;">'+g.text +'</span>'+ '<br/>')
})
Object.defineProperty(s, 'selectable', {
get() {
const kP = s.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef')
return !(kP || s.hoechsterAntritt >= 3)
},
enumerable: true,
configurable: true
})
})
this.distinctPruefungsDates.sort((d1, d2) => {
@@ -910,6 +1003,8 @@ export const Benotungstool = {
field: date,
formatter: this.pruefungFormatter,
titleFormatter: this.pruefungTitleFormatter,
topCalc: this.terminCalcFunc,
topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center",
widthGrow: 1,
minWidth: 200,
@@ -1338,7 +1433,7 @@ export const Benotungstool = {
}
},
getStudentenOptions() {
return this.studenten ? this.studenten : []
return this.studenten ? this.studenten.filter(s => s.selectable) : []
},
getKommPruefCount(){
let counter = 0
@@ -1548,6 +1643,8 @@ export const Benotungstool = {
</template>
</core-filter-cmpt>
</div>
<MobilityLegende/>
`,
};
+20
View File
@@ -0,0 +1,20 @@
export const MobilityLegende = {
name: 'MobilityLegende',
template:`
<div class="col-auto" style="max-width: 60vw">
<!-- TODO: phrasen definieren & verwenden-->
<div class="row" style="font-weight: bold"><h6>Legende</h6></div>
<div class="row"><h6>(i) ... Incoming</h6></div>
<div class="row"><h6>(o) ... Outgoing</h6></div>
<div class="row"><h6>(ar) ... angerechnet </h6></div>
<div class="row"><h6>(iar) ... intern angerechnet</h6></div>
<div class="row"><h6>(nz) ... nicht zugelassen</h6></div>
<div class="row"><h6>(ma) ... MitarbeiterIn</h6></div>
<div class="row"><h6>(a.o.) ... Außerordentliche/r HörerIn</h6></div>
<div class="row"><h6>(d.d.) ... Double Degree Program</h6></div>
</div>
`
};
export default MobilityLegende;