mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-19 16:02:15 +00:00
persistence of order/width/visibility of notenTable predefined columns before applying dynamic pruefung columns in setupData and other table col manipulating routines; fix antrittCalculation reactivity and resulting selection eligibility and the reactivity of that also; added custom sorter for pruefung cols; some minor UX tweaks;
This commit is contained in:
@@ -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 += ('<span style="color: red;">'+g.text +'</span>'+ '<br/>')
|
||||
})
|
||||
|
||||
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) => {
|
||||
|
||||
Reference in New Issue
Block a user