diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js
index 752e3b468..25b7868a2 100644
--- a/public/js/components/Cis/Benotungstool/Benotungstool.js
+++ b/public/js/components/Cis/Benotungstool/Benotungstool.js
@@ -148,20 +148,31 @@ export const Benotungstool = {
// AND the table actually has columns (to avoid saving empty states)
if (!this.stateRestored) return;
-
const rawLayout = table.getColumnLayout();
+ const filteredLayout = rawLayout.filter(col => {
+ if(this.notenTableOptions.columns.some(colDef => colDef.field === col.field)) return col
+ return null
+ })
+
+ // TODO: if dynamic cols have sort/filter/headerfilter functionality filter them here before persisting
+ // into local storage
+ const rawSorters = table.getSorters()
+
+ const rawFilters = table.getFilters()
+
+ const rawHeaderFilters = table.getHeaderFilters()
const state = {
- columns: rawLayout.map(col => ({
+ columns: filteredLayout.map(col => ({
field: col.field,
visible: col.visible,
width: col.width,
})),
- sort: table.getSorters().map(s => ({
+ sort: rawSorters.map(s => ({
field: s.field,
dir: s.dir,
})),
- filters: table.getFilters(),
- headerFilters: table.getHeaderFilters()
+ filters: rawFilters,
+ headerFilters: rawHeaderFilters
};
localStorage.setItem(this.persistenceID, JSON.stringify(state));
@@ -188,31 +199,20 @@ export const Benotungstool = {
table.on("renderComplete", () => {
if (this.stateRestored) return;
- // restore layout first
- 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
- }));
+ // layout restore should be happening in setupData()
- table.setColumnLayout(layout);
- this.colLayoutRestored = true;
- }
-
if (saved?.filters && !this.filtersRestored) {
this.filtersRestored = true;
table.setFilter(saved.filters);
}
-
+
if (saved?.headerFilters && !this.headerFiltersRestored) {
this.headerFiltersRestored = true;
saved.headerFilters.forEach(hf => {
table.setHeaderFilterValue(hf.field, hf.value);
});
}
-
+
if (saved?.sort?.length && !this.sortRestored) {
this.sortRestored = true;
setTimeout(() => {
@@ -507,6 +507,9 @@ export const Benotungstool = {
}).finally(()=>{this.loading = false})
},
handleAddNewPruefungenResponse(res, uids) {
+ // in case we reload when changing lva_id or stsem to always consider local storage layout
+ this.colLayoutRestored = false;
+
const pruefungen = res.data
uids.forEach(entry => {
const saved = pruefungen[entry.uid].savedPruefung?.[0]
@@ -518,6 +521,7 @@ export const Benotungstool = {
// check for extra pruefung (termin1) to add before
if(extra) {
student["Termin1"] = extra
+ student.pruefungen.push(extra) // push to pruefungen array for antritt evaluation
}
this.correctOldTerminTypenForStudent(student, saved)
@@ -545,6 +549,8 @@ export const Benotungstool = {
// recalculate student antritte
student.hoechsterAntritt = this.getAntrittCountStudent(student)
+ this.recalculateSelectable(student)
+ this.reformatStudentRow(student)
})
// add col to table
@@ -563,6 +569,7 @@ export const Benotungstool = {
hozAlign:"center",
widthGrow: 1,
minWidth: 200,
+ width: 250,
visible: true,
tooltip: false
})
@@ -577,9 +584,12 @@ export const Benotungstool = {
field: date,
formatter: this.pruefungFormatter,
titleFormatter: this.pruefungTitleFormatter,
+ topCalc: this.terminCalcFunc,
+ topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center",
widthGrow: 1,
- minWidth: 150,
+ minWidth: 200,
+ width: 250,
visible: true,
tooltip: false
})
@@ -588,14 +598,56 @@ export const Benotungstool = {
if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last
// redraw table
+
+ // preemptively do the persistence part of columns already here so we get the merged definition before
+ // tabulator internal go to war with vue reactives
+ let colsUsed = null
+ const saved = this.loadState();
+ if (saved && saved.columns) {
+ // new columns map for lookup
+ const colMap = new Map(cols.map(c => [c.field, c]));
+
+ const restoredCols = [];
+
+ // add columns in the SAVED order, applying saved width/visibility
+ saved.columns.forEach(savedCol => {
+ const originalDef = colMap.get(savedCol.field);
+ if (originalDef) {
+ restoredCols.push({
+ ...originalDef, // Keep formatters, calcs, etc.
+ width: savedCol.width,
+ visible: savedCol.visible
+ });
+ colMap.delete(savedCol.field); // Remove so we don't double-add
+ }
+ });
+
+ // append any NEW columns aka pruefungs cols
+ colMap.forEach((def) => {
+ restoredCols.push(def);
+ });
+
+ colsUsed = restoredCols;
+ this.colLayoutRestored = true;
+ }
+
this.loading = false
// TODO: find some solution so the tool does not appear "jumpy" after pruefung calls
-
- this.$refs.notenTable.tabulator.setColumns(cols)
+ const colsFinal = colsUsed ?? cols
+ this.$refs.notenTable.tabulator.setColumns(colsFinal)
this.$refs.notenTable.tabulator.setData(this.studenten);
this.$refs.notenTable.tabulator.redraw(true);
},
+ reformatStudentRow(student) {
+ const table = this.$refs.notenTable.tabulator
+ if(!table) return
+
+ const row = table.rowManager.getRowFromDataObject(student)
+
+ const rowComponent = row.getComponent()
+ rowComponent.reformat()
+ },
correctOldTerminTypenForStudent(student, saved) {
// check if student has a preceding pruefung from same type and remove it since in this case
// the new pruefung will have overwritten the old one
@@ -681,20 +733,22 @@ export const Benotungstool = {
selectable: true,
selectableRangeMode: "click", // shift+click
selectablePersistence: false, // reset selection on table reload
- selectableCheck: function(row, e){
- const data = row.getData();
-
- if(data['kommPruef']) return false
- else if(data.hoechsterAntritt >= this.maxAntrittCount) return false // 3 pruefungen counted
-
- return true; // student can be selected to add pruefung
- },
+ selectableCheck: this.selectableCheck,
rowHeight: 40,
rowFormatter: this.fixTabulatorSelectionFormatter,
columns: this.getColumnsDefinition(),
persistence: false,
}
+ },
+ selectableCheck(row, e) {
+ const data = row.getData();
+
+ if(data['kommPruef']) return false
+ else if(data.hoechsterAntritt >= this.maxAntrittCount) return false // 2 or 3 pruefungen counted
+
+ return true; // student can be selected to add pruefung
+
},
getColumnsDefinition() {
const columns = []
@@ -744,7 +798,9 @@ export const Benotungstool = {
handleClick: this.selectAllHandler
},
width: 50,
- cssClass: 'sticky-col'
+ cssClass: 'sticky-col',
+ field: 'selectCol',
+ title: ''
})
columns.push({title: 'UID', field: 'uid', tooltip: false, widthGrow: 1, topCalc: this.sumCalcFunc, cssClass: 'sticky-col'})
columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4mail'))), field: 'email', formatter: this.mailFormatter, tooltip: false, visible: false, widthGrow: 1, variableHeight: true})
@@ -836,6 +892,7 @@ export const Benotungstool = {
columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4kommPruef'))),
field: 'kommPruef', widthGrow: 1,
formatter: this.pruefungFormatter,
+ sorter: this.pruefungSorter,
topCalc: this.terminCalcFunc,
topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center", minWidth: 150, visible: false,
@@ -844,6 +901,13 @@ export const Benotungstool = {
return columns
},
+ pruefungSorter(a, b, aRow, bRow, column, dir, params) {
+ if (a === null || typeof a === "undefined" || a === '') return -1;
+ if (b === null || typeof b === "undefined" || b === '') return 1;
+
+ // sort by notenvalue since pruefungen are in same date by column
+ return a.note - b.note
+ },
selectHandler(e, cell) {
const row = cell.getRow();
@@ -882,7 +946,16 @@ export const Benotungstool = {
const notSelectable = data.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef') || data.hoechsterAntritt >= this.maxAntrittCount
if(notSelectable) {
- row.getElement().children[0]?.children[0]?.remove()
+ const el = row.getElement()
+ el.children[0]?.children[0]?.remove()
+
+ el.classList.remove("tabulator-selectable");
+ el.classList.add("tabulator-unselectable");
+ } else {
+ const el = row.getElement()
+
+ el.classList.add("tabulator-selectable");
+ el.classList.remove("tabulator-unselectable");
}
},
terminCalcFunc(entries) {
@@ -1095,6 +1168,7 @@ export const Benotungstool = {
colDiv.className = classParam ?? 'col-4';
colDiv.style.justifyContent = 'center';
colDiv.style.alignItems = 'center';
+ colDiv.style.alignContent = 'center';
colDiv.style.height = '100%';
if (typeof content === 'string') {
@@ -1107,13 +1181,6 @@ export const Benotungstool = {
}
if(data[field]) {
- // showing date in
-
- // const dateParts = data[field].datum.split('-')
- // const date = `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}`
- //
- // // First column (date)
- // rowDiv.appendChild(createCol(date, 'col-4 d-flex justify-content-center align-items-center'));
const noteDefEntry = data.note ? this.notenOptions.find(n => n.note == data[field].note) : null
@@ -1123,7 +1190,6 @@ export const Benotungstool = {
// no actions on kommPruef allowed
// no actions on termin1 aka pruefung 0 aka ursprüngliche note erlaubt
if(field === 'kommPruef' || field === "Termin1") {
- // rowDiv.appendChild(createCol('', 'col-4 d-flex justify-content-center align-items-center')); // append empty col4 to have formatting similar
return rowDiv
}
@@ -1153,8 +1219,8 @@ export const Benotungstool = {
this.openPruefungModal(data, null, field)
});
- rowDiv.appendChild(createCol(button), 'col-4 d-flex justify-content-center align-items-center');
-
+ rowDiv.appendChild(createCol(button), 'col-4 d-flex justify-content-center align-content-center');
+
return rowDiv;
} else {
return ''
@@ -1243,6 +1309,9 @@ export const Benotungstool = {
this.notenOptionsResolve = resolve
},
async setupData(data){
+ // in case we reload when changing lva_id or stsem to always consider local storage layout
+ this.colLayoutRestored = false;
+
this.studenten = data[0] ?? []
this.studenten.forEach(s => {
s.pruefungen = []
@@ -1321,10 +1390,13 @@ export const Benotungstool = {
else s.teilnote += (''+g.text +''+ '
')
})
+ const vueThis = this
Object.defineProperty(s, 'selectable', {
get() {
const kP = s.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef')
- return !(kP || s.hoechsterAntritt >= this.maxAntrittCount)
+ const maxAntrittReached = s.hoechsterAntritt >= vueThis.maxAntrittCount
+ return !(kP || maxAntrittReached)
+
},
set() {
// empty setter so tabulator doesnt scream
@@ -1342,11 +1414,13 @@ export const Benotungstool = {
field: "Termin1",
formatter: this.pruefungFormatter,
titleFormatter: this.pruefungTitleFormatter,
+ sorter: this.pruefungSorter,
topCalc: this.terminCalcFunc,
topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center",
widthGrow: 1,
minWidth: 200,
+ width: 250,
visible: true,
tooltip: false
})
@@ -1370,11 +1444,13 @@ export const Benotungstool = {
field: date,
formatter: this.pruefungFormatter,
titleFormatter: this.pruefungTitleFormatter,
+ sorter: this.pruefungSorter,
topCalc: this.terminCalcFunc,
topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center",
widthGrow: 1,
minWidth: 200,
+ width: 250,
visible: true,
tooltip: false
})
@@ -1382,11 +1458,43 @@ export const Benotungstool = {
if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last
+
+ // preemptively do the persistence part of columns already here so we get the merged definition before
+ // tabulator internal go to war with vue reactives
+ let colsUsed = null
+ const saved = this.loadState();
+ if (saved && saved.columns) {
+ // new columns map for lookup
+ const colMap = new Map(cols.map(c => [c.field, c]));
+
+ const restoredCols = [];
+
+ // add columns in the SAVED order, applying saved width/visibility
+ saved.columns.forEach(savedCol => {
+ const originalDef = colMap.get(savedCol.field);
+ if (originalDef) {
+ restoredCols.push({
+ ...originalDef, // Keep formatters, calcs, etc.
+ width: savedCol.width,
+ visible: savedCol.visible
+ });
+ colMap.delete(savedCol.field); // Remove so we don't double-add
+ }
+ });
+
+ // append any NEW columns aka pruefungs cols
+ colMap.forEach((def) => {
+ restoredCols.push(def);
+ });
+
+ colsUsed = restoredCols;
+ this.colLayoutRestored = true;
+ }
+
+ const colsFinal = colsUsed ?? cols
this.loading = false
- console.log('setColumns', cols)
-
- this.$refs.notenTable.tabulator.setColumns(cols)
+ this.$refs.notenTable.tabulator.setColumns(colsFinal)
this.$refs.notenTable.tabulator.setData(this.studenten);
this.$refs.notenTable.tabulator.redraw(true);
},
@@ -1567,6 +1675,9 @@ export const Benotungstool = {
s.lv_note = res.data[1]?.note
+ const oldScrollLeft = this.$refs.notenTable.tabulator.rowManager.scrollLeft
+ const oldScrollTop = this.$refs.notenTable.tabulator.rowManager.scrollTop
+
// add new pruefung to row
if(!this.pruefung) {
this.handleAddNewTermin(res.data, s)
@@ -1579,17 +1690,18 @@ export const Benotungstool = {
// antritte might have changed due to different benotung
s.hoechsterAntritt = this.getAntrittCountStudent(s)
+ this.recalculateSelectable(s)
+ this.reformatStudentRow(s)
}
- const oldScrollLeft = this.$refs.notenTable.tabulator.rowManager.scrollLeft
- const oldScrollTop = this.$refs.notenTable.tabulator.rowManager.scrollTop
+
this.$refs.notenTable.tabulator.redraw(true)
Vue.nextTick(()=> {
const table = this.$refs.notenTable.tabulator.element.querySelector('.tabulator-tableholder')
if(table) {
table.scrollLeft = oldScrollLeft;
- table.oldScrollTop = oldScrollTop;
+ table.scrollTop = oldScrollTop;
}
})
@@ -1602,12 +1714,15 @@ export const Benotungstool = {
this.$refs.modalContainerPruefung.hide()
},
handleAddNewTermin(data, student){
+ this.colLayoutRestored = false; // always keep persistence options in mind when modifying table cols dynamically
+
const savedPruefung = data[0]
const extra = data[2]
// check for extra pruefung (termin1) to add before
if(extra) {
student["Termin1"] = extra
+ student.pruefungen.push(extra) // push to pruefungen array for antritt evaluation
}
this.correctOldTerminTypenForStudent(student, savedPruefung)
@@ -1634,7 +1749,10 @@ export const Benotungstool = {
})
// recalculate student antritte
+
student.hoechsterAntritt = this.getAntrittCountStudent(student)
+ this.recalculateSelectable(student)
+ this.reformatStudentRow(student)
// add col to table
const cols = [...this.notenTableOptions.columns.slice(0, -1)];
@@ -1647,11 +1765,13 @@ export const Benotungstool = {
field: "Termin1",
formatter: this.pruefungFormatter,
titleFormatter: this.pruefungTitleFormatter,
+ sorter: this.pruefungSorter,
topCalc: this.terminCalcFunc,
topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center",
widthGrow: 1,
minWidth: 200,
+ width: 250,
visible: true,
tooltip: false
})
@@ -1666,9 +1786,13 @@ export const Benotungstool = {
field: date,
formatter: this.pruefungFormatter,
titleFormatter: this.pruefungTitleFormatter,
+ sorter: this.pruefungSorter,
+ topCalc: this.terminCalcFunc,
+ topCalcFormatter: this.terminCalcFormatter,
hozAlign:"center",
widthGrow: 1,
minWidth: 200,
+ width: 250,
visible: true,
tooltip: false
})
@@ -1676,12 +1800,59 @@ export const Benotungstool = {
if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last
- // set Cols
- this.$refs.notenTable.tabulator.clearSort()
- this.$refs.notenTable.tabulator.setColumns(cols)
- this.$refs.notenTable.tabulator.redraw(true)
+
+ // preemptively do the persistence part of columns already here so we get the merged definition before
+ // tabulator internal go to war with vue reactives
+ let colsUsed = null
+ const saved = this.loadState();
+ if (saved && saved.columns) {
+ // new columns map for lookup
+ const colMap = new Map(cols.map(c => [c.field, c]));
+
+ const restoredCols = [];
+
+ // add columns in the SAVED order, applying saved width/visibility
+ saved.columns.forEach(savedCol => {
+ const originalDef = colMap.get(savedCol.field);
+ if (originalDef) {
+ restoredCols.push({
+ ...originalDef, // Keep formatters, calcs, etc.
+ width: savedCol.width,
+ visible: savedCol.visible
+ });
+ colMap.delete(savedCol.field); // Remove so we don't double-add
+ }
+ });
+
+ // append any NEW columns aka pruefungs cols
+ colMap.forEach((def) => {
+ restoredCols.push(def);
+ });
+
+ colsUsed = restoredCols;
+ this.colLayoutRestored = true;
+ }
+
+
+ const colsFinal = colsUsed ?? cols
+ this.$refs.notenTable.tabulator.setColumns(colsFinal)
+ // this.$refs.notenTable.tabulator.redraw(true)
// redraw table outside this function
},
+ recalculateSelectable(student) {
+ const vueThis = this
+ Object.defineProperty(student, 'selectable', {
+ get() {
+ const kP = student.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef')
+ return !(kP || student.hoechsterAntritt >= vueThis.maxAntrittCount)
+ },
+ set() {
+ // empty setter so tabulator doesnt scream
+ },
+ enumerable: true,
+ configurable: true
+ })
+ },
saveNoteneingabe() {
this.$api.call(ApiNoten.saveStudentenNoten(this.password, this.changedNoten, this.lv_id, this.sem_kurzbz))
.then((res) => {