From 6cf709329345d435f6b84822a8c1d7bc449f2ff9 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 5 Feb 2026 16:49:26 +0100 Subject: [PATCH] testing/implementing more config flags; dont select on certain cols; certain cols only available with certain flags; --- .../controllers/api/frontend/v1/Lehre.php | 108 +++-- .../controllers/api/frontend/v1/Noten.php | 39 +- public/css/Cis4/Benotungstool.css | 6 + .../Cis/Benotungstool/Benotungstool.js | 423 ++++++++++-------- system/phrasesupdate.php | 54 +-- 5 files changed, 369 insertions(+), 261 deletions(-) diff --git a/application/controllers/api/frontend/v1/Lehre.php b/application/controllers/api/frontend/v1/Lehre.php index 10d945a3e..bffde3b48 100644 --- a/application/controllers/api/frontend/v1/Lehre.php +++ b/application/controllers/api/frontend/v1/Lehre.php @@ -18,18 +18,9 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); -//require_once('../../../include/studiengang.class.php'); -//require_once('../../../include/student.class.php'); -//require_once('../../../include/datum.class.php'); -//require_once('../../../include/mail.class.php'); -//require_once('../../../include/benutzerberechtigung.class.php'); -//require_once('../../../include/phrasen.class.php'); -//require_once('../../../include/projektarbeit.class.php'); -//require_once('../../../include/projektbetreuer.class.php'); - class Lehre extends FHCAPI_Controller { - + /** * Object initialization */ @@ -38,39 +29,57 @@ class Lehre extends FHCAPI_Controller parent::__construct([ 'lvStudentenMail' => self::PERM_LOGGED, 'LV' => self::PERM_LOGGED, - 'Pruefungen' => self::PERM_LOGGED + 'Pruefungen' => self::PERM_LOGGED, + 'getZugewieseneLv' => self::PERM_LOGGED, + 'getLeForLv' => self::PERM_LOGGED ]); - + + $this->load->library('PhrasesLib'); + + $this->loadPhrases( + array( + 'global', + 'ui', + 'abgabetool' + ) + ); + + $this->load->helper('hlp_sancho_helper'); + + require_once(FHCPATH . 'include/studiengang.class.php'); + require_once(FHCPATH . 'include/student.class.php'); + require_once(FHCPATH . 'include/projektarbeit.class.php'); + require_once(FHCPATH . 'include/projektbetreuer.class.php'); } //------------------------------------------------------------------------------------------------------------------ // Public methods - /** + /** * constructs the emails of the groups from a lehrveranstaltung */ - public function lvStudentenMail() + public function lvStudentenMail() { - $lehreinheit_id = $this->input->get("lehreinheit_id",TRUE); - - // return early if the required parameter is missing - if(!isset($lehreinheit_id)) - { - $this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL); - } + $lehreinheit_id = $this->input->get("lehreinheit_id",TRUE); - $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); - - $studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id); + // return early if the required parameter is missing + if(!isset($lehreinheit_id)) + { + $this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL); + } - $studentenMails = $this->getDataOrTerminateWithError($studentenMails); + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + + $studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id); + + $studentenMails = $this->getDataOrTerminateWithError($studentenMails); //convert array of objects into array of strings $studentenMails = array_map(function($element){ return $element->mail; }, $studentenMails); - $this->terminateWithSuccess($studentenMails); + $this->terminateWithSuccess($studentenMails); } public function LV($studiensemester_kurzbz, $lehrveranstaltung_id) @@ -80,13 +89,13 @@ class Lehre extends FHCAPI_Controller $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage(), $lehrveranstaltung_id); $result = current($this->getDataOrTerminateWithError($result)); - + $this->terminateWithSuccess($result); } /** * fetches all Pruefungen of a student for a specific lehrveranstaltung - * if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database + * if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database * @param mixed $lehrveranstaltung_id * @return void */ @@ -100,5 +109,46 @@ class Lehre extends FHCAPI_Controller $this->terminateWithSuccess($result); } -} + /** + * fetches all assigned lehrveranstaltungen of a mitarbeiter for a given semester + * @param mixed $uid + * @param mixed $sem_kurzbz + * @return void + */ + public function getZugewieseneLv() { + $uid = $this->input->get("uid",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + // TODO: error messages + + if(!isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + + if (!isset($uid) || isEmptyString($uid)) + $uid = getAuthUID(); + + // querying other ma_uids data requires admin permission + if($uid !== getAuthUID()) { + $this->load->library('PermissionLib'); + $isAdmin = $this->permissionlib->isBerechtigt('admin'); + if(!$isAdmin) $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general'); + } + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $result = $this->LehrveranstaltungModel->getLvForLektorInSemester($sem_kurzbz, $uid); + $data = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($data); + } + + public function getLeForLv() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + +// $this->terminateWithSuccess($this->LehreinheitModel->getLesForLv($lv_id, $sem_kurzbz)); + $this->terminateWithSuccess($this->LehreinheitModel->getAllLehreinheitenForLvaAndMaUid($lv_id, getAuthUID(), $sem_kurzbz)); + } + +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index 8426b42b6..f4d9d3584 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -53,7 +53,6 @@ class Noten extends FHCAPI_Controller 'lehre', 'ui' ]); - require_once(FHCPATH . 'include/mobilitaet.class.php'); $this->load->model('education/LePruefung_model', 'LePruefungModel'); $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); @@ -72,45 +71,38 @@ class Noten extends FHCAPI_Controller public function getCisConfig() { $this->terminateWithSuccess( array( - // TODO // Punkte bei der Noteneingabe anzeigen 'CIS_GESAMTNOTE_PUNKTE' => CIS_GESAMTNOTE_PUNKTE, - // TODO // basically on/of toggle for the points/grade col and the arrow button - // Gibt an ob der Lektor erneut eine LVNote eintragen kann wenn bereits eine Zeugnisnote vorhanden ist (true | false) DEFAULT true 'CIS_GESAMTNOTE_UEBERSCHREIBEN' => CIS_GESAMTNOTE_UEBERSCHREIBEN, // only relevant in punkte calculation in backend -// // Gewichtung der Lehreinheiten bei Noteneintragung true|false // 'CIS_GESAMTNOTE_GEWICHTUNG' => CIS_GESAMTNOTE_GEWICHTUNG, // this one should always be set true since fh prüfungsordnung requires at least 3 antritte (t1+t2+kP) - // Bei Gesamtnote eine zusaetzliche Spalte fuer den 2. Termin anzeigen // 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN2' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN2, // TODO // should in 99% of cases be kept true to enable 4 antritte in total, but if a certain // fh still works with 3 antritte per note this can limit the max number of pruefungen accordingly - // Bei Gesamtnote eine zusaetzliche Spalte fuer den 3. Termin anzeigen - // Erfordert den Eintrag "Termin3" in der Tabelle lehre.tbl_pruefungstyp 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN3' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN3, // used to toggle availability of kommPruef type pruefungen - // Bei Gesamtnote eine zusaetzliche Spalte fuer die kommissionelle Pruefung anlegen 'CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF' => CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF, - - //technically exists but is never used -// // Bei Gesamtnote die Spalte fuer die Quelle der Noten anzeigen (Moodle oder LE) + //technically exists but is never used, could be LE pendant to next flag // 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE, - // basically a toggle for "use teilnoten" and the source is always moodle - // Bei Gesamtnote die Spalte fuer die Quelle der Noten anzeigen (Moodle oder LE) + // setting this to false breaks legacy tool and if that was fixed it wouldnt render any table at all + // anyway so not sure why this even is a config at all. placebo at best + + // TODO: do we really need this? 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE, - // Gibt an ob die Note im Notenfreigabemail enthalten ist oder nicht + + // send a mail when approving grades 'CIS_GESAMTNOTE_FREIGABEMAIL_NOTE' => CIS_GESAMTNOTE_FREIGABEMAIL_NOTE ) ); @@ -263,7 +255,7 @@ class Noten extends FHCAPI_Controller // get all prüfungen with noten held in that semester in that lva $pruefungen = $this->LePruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); $pruefungenData = getData($pruefungen); - + $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN, $grades, $anwresult)); } @@ -348,6 +340,9 @@ class Noten extends FHCAPI_Controller " . $this->p->t('lehre','studiengang') . "\n " . $this->p->t('benotungstool','c4nachname') . "\n " . $this->p->t('benotungstool','c4vorname') . "\n"; + if(defined(CIS_GESAMTNOTE_PUNKTE) && CIS_GESAMTNOTE_PUNKTE) { + $studlist .= "" . $this->p->t('benotungstool','c4punkte') . "\n"; + } $studlist .= "" . $this->p->t('benotungstool','c4grade') . "\n"; $studlist .= "" . $this->p->t('ui','bearbeitetVon') . "\n"; } else { @@ -391,7 +386,9 @@ class Noten extends FHCAPI_Controller $studlist .= "" . trim($note->nachname) . ""; $studlist .= "" . trim($note->vorname) . ""; - // TODO: if defined(CIS_PUNKTE) ... + if(defined(CIS_GESAMTNOTE_PUNKTE) && CIS_GESAMTNOTE_PUNKTE) { + $studlist .= "" . trim($lvgesamtnote->punkte) . ""; + } $studlist .= "" .$note->noteBezeichnung. ""; $studlist .= "" . $lvgesamtnote->mitarbeiter_uid; @@ -407,13 +404,13 @@ class Noten extends FHCAPI_Controller $studlist .= ""; // always send the mail, config toggles data contents - $this->sendEmail($lektorFullName, $lvaFullName, count($result->noten), $emails, $studlist, $betreff); + $this->sendFreigabeEmail($lektorFullName, $lvaFullName, count($result->noten), $emails, $studlist, $betreff); $this->terminateWithSuccess($ret); } - private function sendEmail($lektorFullName, $lvaFullName, $notenCount, $emailAdressen, $studlist, $betreff) + private function sendFreigabeEmail($lektorFullName, $lvaFullName, $notenCount, $emailAdressen, $studlist, $betreff) { $emailAdressen[] = getAuthUID() . "@" . DOMAIN; // also send mail to lektors own adress $adressen = implode(";", $emailAdressen); @@ -984,7 +981,7 @@ class Noten extends FHCAPI_Controller $student_uid = $student->uid; $typ = $student->typ; $note = 9; //$result->note; // TODO: parameterize for import maybe - $punkte = ''; // new pruefungen never have punkte, TODO: check if null or '' prefered + $punkte = null; // new pruefungen never have punkte, $lehreinheit_id = $student->lehreinheit_id; $ret[$student->uid] = $this->savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum); @@ -1017,7 +1014,7 @@ class Noten extends FHCAPI_Controller $typ = $pruefung->typ; $note = $pruefung->note; // TODO: parameterize for import maybe $datum = $pruefung->datum; - $punkte = ''; // TODO: check punkte feature + $punkte = null; $lehreinheit_id = $pruefung->lehreinheit_id; $ret[$student_uid] = $this->savePruefungstermin($typ, $student_uid, $lv_id, $sem_kurzbz, $lehreinheit_id, $note, $punkte, $datum); diff --git a/public/css/Cis4/Benotungstool.css b/public/css/Cis4/Benotungstool.css index e58b4b945..dc11d81a7 100644 --- a/public/css/Cis4/Benotungstool.css +++ b/public/css/Cis4/Benotungstool.css @@ -23,6 +23,12 @@ } +/* styling for points input column for notenvorschläge in benotungstool*/ +#notentable .tabulator-tableholder .tabulator-editable[tabulator-field="punkte"] { + position: relative; + background-color: rgba(255, 255, 157, 0.73); +} + /* styling for editable dropdown column of notenvorschläge in benotungstool*/ #notentable .tabulator-tableholder .tabulator-editable[tabulator-field="note_vorschlag"] { position: relative; diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 71457a2dc..7f23ca8d6 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -119,18 +119,9 @@ export const Benotungstool = { const row = cell.getRow() row.reformat() // trigger reformat of arrow - } else if (field === 'punkte') { - const newValue = cell.getValue(); - if(newValue == '' || newValue == null) return - this.$api.call(ApiNoten.getNoteByPunkte(newValue, this.lv_id, this.sem_kurzbz)).then(res => { - if(res?.meta?.status === 'success' && res.data >= 0) { - const row = cell.getRow(); - row.update({note_vorschlag: res.data}) - } - }) } } - }, + }, { event: "cellClick", handler: async (e, cell) => { @@ -139,18 +130,68 @@ export const Benotungstool = { if(field == "mobility_zusatz") { this.$refs.drawer.show() e.stopPropagation() + this.undoSelection(cell) + } else if (field == "punkte" || field == "note_vorschlag" || field == "übernehmen") { + this.undoSelection(cell) } } } ]}; }, methods: { + undoSelection(cell) { + // checks if cells row is selected and unselects -> imitates columns which dont trigger row selection + // but actually just revert it after the fact + + const row = cell.getRow() + if(row.isSelected()) { + row.deselect(); + } + }, + // using this to expose input event of editor element properly, tabulator makes it hard to access on default editor + // implemented after tabulator/src/js/modules/edit/defaults/editors/number.js + liveNumberEditor(cell, onRendered, success, cancel) { + const editor = document.createElement("input"); + editor.setAttribute("type", "number"); + editor.value = cell.getValue(); + + const row = cell.getRow() + const rowData = row.getData() + + rowData._debouncedFetchNoteForPunkte = debounce(this.fetchNoteForPunkte, 500) + editor.addEventListener("input", (e) => { + rowData._debouncedFetchNoteForPunkte(e.target.value, row) + }); + + onRendered(() => { + editor.focus(); + editor.style.height = "100%"; + }); + + editor.addEventListener("change", () => success(editor.value)); + editor.addEventListener("blur", () => success(editor.value)); + editor.addEventListener("keydown", (e) => { + if (e.keyCode === 13) success(editor.value); + if (e.keyCode === 27) cancel(); + }); + + return editor; + }, + fetchNoteForPunkte(valueParam, row) { + const value = valueParam == '' ? null : valueParam + this.$api.call(ApiNoten.getNoteByPunkte(value, this.lv_id, this.sem_kurzbz)).then(res => { + if(res?.meta?.status === 'success' && res.data >= 0) { + row.update({note_vorschlag: res.data}) + } + }) + }, fetchNoteForPunktePruefung(event) { - this.$api.call(ApiNoten.getNoteByPunkte(event.value, this.lv_id, this.sem_kurzbz)).then(res => { + const value = event.value == '' ? null : event.value + this.$api.call(ApiNoten.getNoteByPunkte(value, this.lv_id, this.sem_kurzbz)).then(res => { if(res?.meta?.status === 'success' && res.data >= 0) { this.selectedPruefungNote = this.notenOptions.find(n => n.note == res.data) } - }) + }) }, isValidDate_ddmmyyyy(str) { if (typeof str !== 'string') return false; @@ -219,12 +260,15 @@ export const Benotungstool = { // in case we need to further validate noten, currently parser does all }, parseNote(rowParts, notenbulk, rowNum) { + debugger const id = this.identifyUid(rowParts[0]) + const idTrimmed = rowParts[0].trim() let student = null + if(id === 'matrikelnr') { // find student by matrnr and use uid later on - student = this.studenten.find(s => s.matrikelnr === rowParts[0]) + student = this.studenten.find(s => s.matrikelnr?.trim() === idTrimmed) } else if(id === 'uid') { - student = this.studenten.find(s => s.uid === uid) + student = this.studenten.find(s => s.uid?.trim() === idTrimmed) } if(!student) { this.$fhcAlert.alertWarning('Kein Student gefunden für ID ' + rowParts[0] + ' in Zeile Nr. ' + rowNum + ' Die Zeile wurde übersprungen.') @@ -471,6 +515,8 @@ export const Benotungstool = { }, getNotenTableOptions() { return { + // debugEventsExternal:true, + // debugEventsInternal:true, height: 700, virtualDom: false, index: 'uid', @@ -489,152 +535,157 @@ export const Benotungstool = { }, rowHeight: 40, rowFormatter: this.fixTabulatorSelectionFormatter, - columns: [ - { - formatter: function (cell, formatterParams, onRendered) { - // create the built-in checkbox - 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); - } - }); - - return checkbox; - }, - 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.selectHandler - }, - titleFormatterParams: { - handleClick: this.selectAllHandler - }, - width: 50, - cssClass: 'sticky-col' - }, - {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1, topCalc: this.sumCalcFunc, cssClass: 'sticky-col'}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4mail'))), field: 'email', formatter: this.mailFormatter, tooltip: false, visible: false, widthGrow: 1, variableHeight: true}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4antrittCountv2'))), field: 'hoechsterAntritt', tooltip: false, widthGrow: 1}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4vorname'))), field: 'vorname', headerFilter: true, tooltip: false, widthGrow: 1}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4nachname'))), field: 'nachname', headerFilter: true, widthGrow: 1}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4anwesenheitsquote'))), field: 'anwquote', widthGrow: 1, formatter: this.percentFormatter}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4mobility'))), field: 'mobility_zusatz', headerFilter: true, widthGrow: 1, visible: false}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4teilnoten'))), field: 'teilnote', widthGrow: 1, formatter: this.teilnotenFormatter, variableHeight: true}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4punkte'))), field: 'punkte', widthGrow: 1, - editor: 'number', - editorParams: (cell) => { - return { - min: 0, - max: 9999, - step: 1, - elementAttributes: { - maxlength: "4" - }, - selectContents: true, - verticalNavigation: "table" - } - }, - variableHeight: true - }, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4notenvorschlag'))), field: 'note_vorschlag', - editor: 'list', - editorParams: (cell) => { - // write original cell value into row to it can be retrieved if edit is cancelled without selection - const rowData = cell.getRow().getData(); - rowData._originalNoteVorschlag = cell.getValue(); - - return { - values: this.notenOptionsLehre.map(opt => ({ - label: opt.bezeichnung, - value: opt.note - })) - }; - }, - editable: (cell) => { - // TODO: css style this a bit - // punkte features enables mapping but unable to set note directly - if(this.config?.CIS_GESAMTNOTE_PUNKTE) return false - const rowData = cell.getRow().getData(); - const noteOption = this.notenOptions.find(opt => opt.note == rowData.note) - 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 - - return noteOption.lkt_ueberschreibbar - }, - formatter: (cell) => { - const rowData = cell.getRow().getData(); - 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) - let style = '' - - if(val === undefined) return '' - if(p || !match?.lkt_ueberschreibbar) style = 'color: gray;font-style: italic; background-color: #f0f0f0;pointer-events: none;opacity: 0.6;user-select: none;cursor: not-allowed;' - return '
' + val + '
' - }, - widthGrow: 1 - }, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4vorschlag_übernehmen'))), width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote, variableHeight: true}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4lvnote'))), field: 'lv_note', - formatter: this.notenFormatter, - 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.$capitalize(this.$p.t('benotungstool/c4freigabe'))), field: 'freigegeben', widthGrow: 1, formatter: this.freigabeFormatter, variableHeight: true}, - {title: Vue.computed(() => this.$capitalize(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.$capitalize(this.$p.t('benotungstool/c4kommPruef'))), - field: 'kommPruef', widthGrow: 1, - formatter: this.pruefungFormatter, - topCalc: this.terminCalcFunc, - topCalcFormatter: this.terminCalcFormatter, - hozAlign:"center", minWidth: 150, visible: false - } - ], + columns: this.getColumnsDefinition(), persistence: false, } }, + getColumnsDefinition() { + const columns = [] + + + columns.push({ + formatter: function (cell, formatterParams, onRendered) { + // create the built-in checkbox + 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); + } + }); + + return checkbox; + }, + 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.selectHandler + }, + titleFormatterParams: { + handleClick: this.selectAllHandler + }, + width: 50, + cssClass: 'sticky-col' + }) + 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}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4antrittCountv2'))), field: 'hoechsterAntritt', tooltip: false, widthGrow: 1}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4vorname'))), field: 'vorname', headerFilter: true, tooltip: false, widthGrow: 1}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4nachname'))), field: 'nachname', headerFilter: true, widthGrow: 1}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4anwesenheitsquote'))), field: 'anwquote', widthGrow: 1, formatter: this.percentFormatter}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4mobility'))), field: 'mobility_zusatz', headerFilter: true, widthGrow: 1, visible: false}) + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE) { + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4teilnoten'))), field: 'teilnote', widthGrow: 1, formatter: this.teilnotenFormatter, variableHeight: true}) + } + if(this.config?.CIS_GESAMTNOTE_PUNKTE) { + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4punkte'))), field: 'punkte', widthGrow: 1, + editor: this.liveNumberEditor, + editable: (cell) => { + const rowData = cell.getRow().getData(); + if(this.pruefungen?.find(p => p.student_uid == rowData.uid)) return false + + return true + }, + variableHeight: true + }) + } + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4notenvorschlag'))), field: 'note_vorschlag', + editor: 'list', + editorParams: (cell) => { + // write original cell value into row to it can be retrieved if edit is cancelled without selection + const rowData = cell.getRow().getData(); + rowData._originalNoteVorschlag = cell.getValue(); + + return { + values: this.notenOptionsLehre.map(opt => ({ + label: opt.bezeichnung, + value: opt.note + })) + }; + }, + editable: (cell) => { + // punkte features enables mapping but unable to set note directly + if(this.config?.CIS_GESAMTNOTE_PUNKTE) return false + const rowData = cell.getRow().getData(); + const noteOption = this.notenOptions.find(opt => opt.note == rowData.note) + 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 + + return noteOption.lkt_ueberschreibbar + }, + formatter: (cell) => { + const rowData = cell.getRow().getData(); + 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) + let style = '' + + if(val === undefined) return '' + if(p || !match?.lkt_ueberschreibbar) style = 'color: gray;font-style: italic; background-color: #f0f0f0;pointer-events: none;opacity: 0.6;user-select: none;cursor: not-allowed;' + return '
' + val + '
' + }, + widthGrow: 1 + }) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4vorschlag_übernehmen'))), field: 'übernehmen', width: 150, hozAlign: 'center', formatter: this.arrowFormatter, + // cellClick: this.saveNote, + variableHeight: true}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4lvnote'))), field: 'lv_note', + formatter: this.notenFormatter, + 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 + }) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4freigabe'))), field: 'freigegeben', widthGrow: 1, formatter: this.freigabeFormatter, variableHeight: true}) + columns.push({title: Vue.computed(() => this.$capitalize(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 + }) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4kommPruef'))), + field: 'kommPruef', widthGrow: 1, + formatter: this.pruefungFormatter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", minWidth: 150, visible: false + }) + + return columns + }, selectHandler(e, cell) { const row = cell.getRow(); @@ -789,6 +840,10 @@ export const Benotungstool = { return value }, saveNote(e, cell) { // Notenvorschlag freigeben + // TODO: rename & rework so it handles with a button + + // TODO: save this method once we decide the arrow was actually very cool + const row = cell.getRow() const data = row.getData() @@ -812,9 +867,6 @@ export const Benotungstool = { }).finally(()=>this.loading = false) - }, - punkteFormatter(cell) { - }, teilnotenFormatter(cell) { const val = cell.getValue() @@ -843,7 +895,6 @@ export const Benotungstool = { const canAdd = field !== 'kommPruef' && data.hoechsterAntritt < 4 && !colDef.originalNote // TODO: check for some time limit maybe? old pruefungen can be changed/created - // TODO: it also looks ugly and unprofessional, should at some peoplt disable/hide the change action // Create root row div const rowDiv = document.createElement('div'); @@ -892,6 +943,7 @@ export const Benotungstool = { } if(data[field]) { + // showing date in // const dateParts = data[field].datum.split('-') @@ -912,16 +964,18 @@ export const Benotungstool = { return rowDiv } - // Third column (button) - const button = document.createElement('button'); - button.className = 'btn btn-outline-secondary'; - button.textContent = this.$capitalize(this.$p.t('benotungstool/changePruefungButtonText')); - button.addEventListener('click', () => { - this.openPruefungModal(data, data[field], field); - }); - - rowDiv.appendChild(createCol(button, 'col-4 d-flex justify-content-center align-items-center')); + if(data[field]?.pruefungstyp_kurzbz !== 'Termin1') { + // Third column (button) + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = this.$capitalize(this.$p.t('benotungstool/changePruefungButtonText')); + button.addEventListener('click', () => { + this.openPruefungModal(data, data[field], field); + }); + rowDiv.appendChild(createCol(button, 'col-4 d-flex justify-content-center align-items-center')); + } + return rowDiv; } else if (canAdd) { // return new btn action @@ -984,14 +1038,27 @@ export const Benotungstool = { let style = 'display: flex; justify-content: center; align-items: center; height: 100%;' - if(!data.note_vorschlag || (data.note_vorschlag == data.lv_note)) { // uncolored arrow - return '
' + - '
' + if(!data.note_vorschlag || (data.note_vorschlag == data.lv_note)) { + // arrow to ambiguous in meaning, use str8 forward worded button here instead + // uncolored arrow + // return '
' + + // '
' + + return '' } - // can save a notenvorschlag -> colored - return '
' + - '
' + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = this.$capitalize(this.$p.t('benotungstool/c4notenvorschlagUebernehmen')); + button.addEventListener('click', () => { + this.saveNote(data) + console.log('button click') + }); + return button; + + // // can save a notenvorschlag -> colored + // return '
' + + // '
' }, mailFormatter(cell) { const val = cell.getValue() @@ -1676,10 +1743,10 @@ export const Benotungstool = { return cs }, getNotenfreigabeHinweistext() { - return this.$capitalize(this.$p.t('benotungstool/notenfreigabeHinweistextv3')) + return this.$capitalize(this.$p.t('benotungstool/notenfreigabeHinweistextv4')) }, getNotenimportHinweistext() { - return this.$capitalize(this.$p.t('benotungstool/notenimportHinweistextv3')) + return this.$capitalize(this.$p.t('benotungstool/notenimportHinweistextv5')) } }, created() { diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index ed9595afa..8e151bcf9 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -54320,26 +54320,24 @@ and represent the current state of research on the topic. The prescribed citatio array( 'app' => 'core', 'category' => 'benotungstool', - 'phrase' => 'notenfreigabeHinweistextv3', + 'phrase' => 'notenfreigabeHinweistextv4', 'insertvon' => 'system', 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Notenfreigabe -

+ 'text' => ' Wenn alle einzutragenden Noten vermerkt sind (Nachtragung jederzeit möglich) können diese per Klick auf Freigabe für die Studiengangsassistenz freigegeben werden. -

+
Aus Gründen der erhöhten Sicherheit ist bei der Freigabe der Noten die Eingabe Ihres Passwortes erforderlich.', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Grade Release -

- Once all grades to be entered have been recorded (additions are possible at any time), they can be released to the program assistant by clicking on "Release." -

- For increased security reasons, you will be required to enter your password when releasing grades.2)', + 'text' => ' + Once all grades to be entered have been recorded (additions are possible at any time), they can be released to the program assistant by clicking on "Confirm Grades" +
+ For increased security reasons, you will be required to enter your password when releasing grades.', 'description' => '', 'insertvon' => 'system' ) @@ -54348,38 +54346,28 @@ and represent the current state of research on the topic. The prescribed citatio array( 'app' => 'core', 'category' => 'benotungstool', - 'phrase' => 'notenimportHinweistextv3', + 'phrase' => 'notenimportHinweistextv5', 'insertvon' => 'system', 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Laden Sie sich die Notenliste im Excel-Format unter CIS → Lehrveranstaltungen → Anwesenheits- und Notenlisten → Notenliste herunter. -

- Tragen Sie die Noten in das Dokument und speichern Sie dieses. -

- Markieren Sie im Excel-Dokument die Inhalte der Spalten Personenkennzeichen und Note für jene Studierende, deren Noten Sie importieren möchten (ohne Überschrift !) -

- Kopieren Sie die markierten Inhalte mittels strg + c oder Bearbeiten → Kopieren in die Zwischenablage -

- Einfügen der Inhalte mittels strg + v oder Bearbeiten → Einfügen -

- Mit einem Klick auf Import werden die Noten übernommen.', + 'text' => '• Laden Sie sich die Notenliste im Excel-Format unter CIS → Lehrveranstaltungen → Anwesenheits- und Notenlisten → Notenliste herunter.
+ • Tragen Sie die Noten in das Dokument und speichern Sie dieses.
+ • Markieren Sie im Excel-Dokument die Inhalte der Spalten Personenkennzeichen und Note für jene Studierende, deren Noten Sie importieren möchten (ohne Überschrift !)
+ • Kopieren Sie die markierten Inhalte mittels strg + c oder Bearbeiten → Kopieren in die Zwischenablage
+ • Einfügen der Inhalte mittels strg + v oder Bearbeiten → Einfügen
+ • Mit einem Klick auf Import werden die Noten übernommen.
', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Download the grade list in Excel format from CIS → Courses → Attendance and Grade Lists → Grade List. -

- Enter the grades into the document and save it. -

- In the Excel document, select the contents of the Person ID and Grade columns for the students whose grades you want to import (without headings!). -

- Copy the selected content to the clipboard using Ctrl + c or Edit → Copy. -

- Paste the content using Ctrl + v or Edit → Paste. -

- Click Import to import the grades.', + 'text' => '• Download the grade list in Excel format from CIS → Courses → Attendance and Grade Lists → Grade List.
+ • Enter the grades into the document and save it.
+ • In the Excel document, select the contents of the Person ID and Grade columns for the students whose grades you want to import (without headings!).
+ • Copy the selected content to the clipboard using Ctrl + c or Edit → Copy.
+ • Paste the content using Ctrl + v or Edit → Paste.
+ • Click Import to import the grades.
', 'description' => '', 'insertvon' => 'system' ) @@ -54468,7 +54456,7 @@ and represent the current state of research on the topic. The prescribed citatio array( 'app' => 'core', 'category' => 'benotungstool', - 'phrase' => 'c4vorschlag_übernehmen', + 'phrase' => 'c4notenvorschlagUebernehmen', 'insertvon' => 'system', 'phrases' => array( array(