From b1a1cdf23550fc42337a944d4661f72d582b809d Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 6 Nov 2025 16:29:24 +0100 Subject: [PATCH] studiensemester dropdown filter, default all, options are current/next and op to 10 back; benotet/unbenotet/alle fetch parameter; WIP orgform/studstatus cols; --- .../controllers/api/frontend/v1/Abgabe.php | 8 +- .../v1/organisation/Studiensemester.php | 16 ++- .../models/education/Projektarbeit_model.php | 13 ++- public/js/api/factory/abgabe.js | 6 +- public/js/api/factory/studiengang.js | 11 +-- public/js/api/factory/studiensemester.js | 25 +++++ .../Cis/Abgabetool/AbgabetoolAssistenz.js | 97 ++++++++++++++----- 7 files changed, 135 insertions(+), 41 deletions(-) create mode 100644 public/js/api/factory/studiensemester.js diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index 73f0d0a15..df687acea 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -864,12 +864,13 @@ class Abgabe extends FHCAPI_Controller public function getProjektarbeitenForStudiengang() { $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); - $studiengang_kz = $this->input->get("studiengang_kz",TRUE); - + $studiengang_kz = $this->input->get("studiengang_kz", TRUE); + $benotet = $this->input->get("benotet", TRUE); + if (!isset($studiengang_kz) || isEmptyString($studiengang_kz)) $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); - $result = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz); + $result = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz, $benotet); $projektarbeiten = $this->getDataOrTerminateWithError($result); if(count($projektarbeiten) == 0) { // avoid further abgabetermin queries if the are no projektarbeiten @@ -899,6 +900,7 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithSuccess(array($projektarbeiten, DOMAIN)); } + // TODO: this could be in a generic info controller and resused public function getStudiengaenge() { $this->load->library('PermissionLib'); diff --git a/application/controllers/api/frontend/v1/organisation/Studiensemester.php b/application/controllers/api/frontend/v1/organisation/Studiensemester.php index bb56ea71a..06d5b93c9 100644 --- a/application/controllers/api/frontend/v1/organisation/Studiensemester.php +++ b/application/controllers/api/frontend/v1/organisation/Studiensemester.php @@ -25,7 +25,8 @@ class Studiensemester extends FHCAPI_Controller array( 'getAll' => self::PERM_LOGGED, 'getAktNext' => self::PERM_LOGGED, - 'getStudienjahrByStudiensemester' => self::PERM_LOGGED + 'getStudienjahrByStudiensemester' => self::PERM_LOGGED, + 'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED ) ); // Load model StudiensemesterModel @@ -152,4 +153,17 @@ class Studiensemester extends FHCAPI_Controller $this->terminateWithSuccess((getData(success($studienjahrObj)))); } + + public function getAllStudiensemesterAndAktOrNext() { + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + $this->StudiensemesterModel->addOrder("start", "DESC"); + $result = $this->StudiensemesterModel->getAktOrNextSemester(); + $aktuell = getData($result)[0]; + + $result = $this->StudiensemesterModel->getPreviousFrom($aktuell->studiensemester_kurzbz, 10); + $studiensemester = getData($result); + + $this->terminateWithSuccess(array($studiensemester, $aktuell)); + } } diff --git a/application/models/education/Projektarbeit_model.php b/application/models/education/Projektarbeit_model.php index 1f35ea856..0f0461d91 100644 --- a/application/models/education/Projektarbeit_model.php +++ b/application/models/education/Projektarbeit_model.php @@ -319,7 +319,7 @@ class Projektarbeit_model extends DB_Model return $version === null ? null : $version->isCurrent; } - public function getProjektarbeitenForStudiengang($studiengang_kz) { + public function getProjektarbeitenForStudiengang($studiengang_kz, $benotet) { $new_qry = "SELECT DISTINCT ON(tmp.projektarbeit_id) *, campus.get_betreuer_details(tmp.zweitbetreuer_person_id) as zweitbetreuer_full_name, campus.get_betreuer_details(tmp.betreuer_person_id) as erstbetreuer_full_name FROM( SELECT @@ -407,8 +407,15 @@ class Projektarbeit_model extends DB_Model LEFT JOIN public.tbl_benutzer betreuer_benutzer ON (betreuer_person.person_id = betreuer_benutzer.person_id) WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom') AND student_benutzer.aktiv AND (lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbegutachter' OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Begutachter') - AND public.tbl_studiengang.studiengang_kz = ? - ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC + AND public.tbl_studiengang.studiengang_kz = ?"; + + if($benotet == 0) { + $new_qry .= " AND lehre.tbl_projektarbeit.note IS NULL "; + } else if ($benotet == 1) { + $new_qry .= " AND lehre.tbl_projektarbeit.note IS NOT NULL "; + } + + $new_qry .= " ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC ) as tmp"; return $this->execReadOnlyQuery($new_qry, array($studiengang_kz)); diff --git a/public/js/api/factory/abgabe.js b/public/js/api/factory/abgabe.js index 12615ee76..417e5fab3 100644 --- a/public/js/api/factory/abgabe.js +++ b/public/js/api/factory/abgabe.js @@ -100,14 +100,14 @@ export default { url: '/api/frontend/v1/Abgabe/getNoten' }; }, - getProjektarbeitenForStudiengang(studiengang_kz) { + getProjektarbeitenForStudiengang(studiengang_kz, benotet = 0) { return { method: 'get', url: '/api/frontend/v1/Abgabe/getProjektarbeitenForStudiengang', - params: { studiengang_kz } + params: { studiengang_kz, benotet } }; }, - // TODO: this could also very well be generic info api :^) + // TODO: this could also very well be generic info api getStudiengaenge() { return { method: 'get', diff --git a/public/js/api/factory/studiengang.js b/public/js/api/factory/studiengang.js index 6d5ae15aa..12322cb3a 100644 --- a/public/js/api/factory/studiengang.js +++ b/public/js/api/factory/studiengang.js @@ -16,17 +16,10 @@ */ export default { - studiengangInformation() { + getAllStudiensemesterAndAktOrNext() { return { method: 'get', - url: '/api/frontend/v1/Studgang/getStudiengangInfo' + url: '/api/frontend/v1/Studiensemester/getStudiengangInfo' }; }, - getStudiengangByKz(studiengang_kz) { - return { - method: 'get', - url: '/api/frontend/v1/organisation/StudiengangEP/getStudiengangByKz', - params: { studiengang_kz } - }; - } }; \ No newline at end of file diff --git a/public/js/api/factory/studiensemester.js b/public/js/api/factory/studiensemester.js new file mode 100644 index 000000000..82a4ee5cc --- /dev/null +++ b/public/js/api/factory/studiensemester.js @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2025 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export default { + getAllStudiensemesterAndAktOrNext() { + return { + method: 'get', + url: '/api/frontend/v1/organisation/Studiensemester/getAllStudiensemesterAndAktOrNext', + }; + } +}; \ No newline at end of file diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index 0ca163e33..e1087b4f2 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -5,6 +5,7 @@ import BsModal from '../../Bootstrap/Modal.js'; import BsOffcanvas from '../../Bootstrap/Offcanvas.js'; import VueDatePicker from '../../vueDatepicker.js.php'; import ApiAbgabe from '../../../api/factory/abgabe.js' +import ApiStudiensemester from '../../../api/factory/studiensemester.js'; import AbgabeterminStatusLegende from "./StatusLegende.js"; // spoofed date testing @@ -52,6 +53,8 @@ export const AbgabetoolAssistenz = { }, data() { return { + allSem: null, + curSem: null, notenOptionFilter: null, inplaceToggle: false, headerFiltersRestored: false, @@ -126,7 +129,8 @@ export const AbgabetoolAssistenz = { {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true,responsive:2, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, - {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))), field: 'note', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, + {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))), field: 'note_bez', headerFilter: true, + responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, visible: false, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuer'))), field: 'erstbetreuer', headerFilter: true, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1}, @@ -166,6 +170,22 @@ export const AbgabetoolAssistenz = { ]}; }, methods: { + semesterChanged(e) { + if(this.$refs.abgabeTable.tabulator) { + const table = this.$refs.abgabeTable.tabulator + + // TODO: maybe check if existing synergy really works with many filters + const existing = table.getFilters().filter(f => f.field != 'studiensemester_kurzbz'); + + const compVal = e.value.studiensemester_kurzbz == 'Alle' ? '' : e.value.studiensemester_kurzbz + const compType = e.value.studiensemester_kurzbz == 'Alle' ? '!=' : '=' + const newFilter = { field: "studiensemester_kurzbz", type: compType, value: compVal }; + + // merge and reapply + table.setFilter([...existing, newFilter]); + } + + }, checkAbgabetermineProjektarbeit(projekt) { // calculate Abgabetermin time diff to now and assign last and next to projekt projekt.abgabetermine.forEach(termin => { @@ -311,12 +331,12 @@ export const AbgabetoolAssistenz = { getOptionLabelStg(option){ return option.kurzbzlang + ' ' + option.bezeichnung }, + getOptionLabelStudiensemester(option){ + return option.studiensemester_kurzbz + }, getNotenFilterOptionLabel(option) { return option.bezeichnung }, - sgChanged(e) { - debugger - }, formatDate(dateParam) { if(dateParam === null) return '' const date = new Date(dateParam) @@ -437,9 +457,6 @@ export const AbgabetoolAssistenz = { this.$refs.abgabeTable.tabulator.deselectRow() const mappedData = this.mapProjekteToTableData(this.projektarbeiten) - - console.log('this.projektarbeiten', this.projektarbeiten) - console.log('mappedData', mappedData) this.$refs.abgabeTable.tabulator.clearData() this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns) @@ -462,6 +479,13 @@ export const AbgabetoolAssistenz = { this.checkAbgabetermineProjektarbeit(projekt) + if(this.notenOptions && projekt.note) { + const opt = this.notenOptions.find(n => n.note == projekt.note) + + // TODO: mehrsprachig englisch + projekt.note_bez = opt.bezeichnung + } + return { ...projekt, abgabetermine: projekt.abgabetermine, @@ -666,7 +690,10 @@ export const AbgabetoolAssistenz = { }, loadProjektarbeiten(all = false, callback) { this.loading = true - this.$api.call(ApiAbgabe.getProjektarbeitenForStudiengang(this.getCurrentStudiengang)) + this.$api.call(ApiAbgabe.getProjektarbeitenForStudiengang( + this.getCurrentStudiengang, + this.notenOptionFilter?.benotet ?? 0 + )) .then(res => { if(res?.data) this.setupData(res.data) }).finally(() => { @@ -701,7 +728,9 @@ export const AbgabetoolAssistenz = { this.tableBuiltPromise = new Promise(this.tableResolve) await this.tableBuiltPromise - this.loadProjektarbeiten() + + // called through notenOptionFilter watcher on startup + // this.loadProjektarbeiten() // this.$refs.verticalsplit.collapseBottom() this.calcMaxTableHeight() @@ -717,6 +746,11 @@ export const AbgabetoolAssistenz = { watch: { selectedStudiengangOption(newVal, oldVal) { this.loadProjektarbeiten() + }, + notenOptionFilter(newVal) { + // that single where clause is worth a decent load time so rather not filter tabulator but just + // adapt the qry + this.loadProjektarbeiten() } }, computed: { @@ -745,6 +779,17 @@ export const AbgabetoolAssistenz = { this.loading = false }) + this.$api.call(ApiStudiensemester.getAllStudiensemesterAndAktOrNext()).then((res) => { + this.allSem = res.data[0] + this.curSem = res.data[1] + + // TODO: maybe filter only for available semester from projektarbeiten dataset + this.studiensemesterOptions = [{studiensemester_kurzbz: 'Alle'}, ...this.allSem] + }).catch(e => { + this.loading = false + }) + + // fetch noten options //TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API this.$api.call(ApiAbgabe.getNoten()).then(res => { @@ -759,16 +804,16 @@ export const AbgabetoolAssistenz = { // this selection is about graded projektarbeiten, so take different options here this.allowedNotenFilterOptions = [ { - bezeichnung: this.$p.t('abgabetool/keineNoteEingetragen'), - note: null, + bezeichnung: Vue.computed(() => this.$p.t('abgabetool/keineNoteEingetragen')), + benotet: 0, }, { - bezeichnung: this.$p.t('abgabetool/c4benotet'), - note: 1, + bezeichnung: Vue.computed(() => this.$p.t('abgabetool/c4benotet')), + benotet: 1, }, { - bezeichnung: Vue.computed(this.$p.t('abgabetool/showAll')), - note: -1, + bezeichnung: Vue.computed(() => this.$p.t('abgabetool/showAll')), + benotet: -1, }, ] @@ -882,7 +927,7 @@ export const AbgabetoolAssistenz = { -
+