studiensemester dropdown filter, default all, options are current/next and op to 10 back; benotet/unbenotet/alle fetch parameter; WIP orgform/studstatus cols;

This commit is contained in:
Johann Hoffmann
2025-11-06 16:29:24 +01:00
parent 3878fce625
commit b1a1cdf235
7 changed files with 135 additions and 41 deletions
@@ -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');
@@ -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));
}
}
@@ -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));
+3 -3
View File
@@ -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',
+2 -9
View File
@@ -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 }
};
}
};
+25
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
export default {
getAllStudiensemesterAndAktOrNext() {
return {
method: 'get',
url: '/api/frontend/v1/organisation/Studiensemester/getAllStudiensemesterAndAktOrNext',
};
}
};
@@ -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 = {
</template>
<!-- TODO: take care of absolute offset values -->
<div class="row">
<div class="row" style="margin-bottom: 12px;">
<Inplace
closable
:closeButtonProps="{
@@ -984,8 +1029,7 @@ export const AbgabetoolAssistenz = {
</div>
<div class="col-3">
<Dropdown
@change="sgChanged"
:placeholder="$p.t('lehre/studiengang')"
:placeholder="$capitalize($p.t('lehre/studiengang'))"
:style="{'width': '100%', 'scroll-behavior': 'auto !important'}"
:optionLabel="getOptionLabelStg"
v-model="selectedStudiengangOption"
@@ -1000,14 +1044,12 @@ export const AbgabetoolAssistenz = {
</div>
<div class="col-3">
<Dropdown
@change="sgChanged"
:placeholder="$p.t('lehre/note')"
:style="{'width': '100%', 'scroll-behavior': 'auto !important'}"
:optionLabel="getNotenFilterOptionLabel"
v-model="notenOptionFilter"
:options="allowedNotenFilterOptions"
showClear
:tabindex="2"
:tabindex="3"
>
<template #optionsgroup="slotProps">
<div>{{ option.bezeichnung }} </div>
@@ -1032,7 +1074,18 @@ export const AbgabetoolAssistenz = {
:useSelectionSpan="false"
>
<template #actions>
<Dropdown
@change="semesterChanged"
:placeholder="$capitalize($p.t('lehre/studiensemester'))"
:style="{'scroll-behavior': 'auto !important'}"
:optionLabel="getOptionLabelStudiensemester"
v-model="curSem"
:options="studiensemesterOptions"
>
<template #optionsgroup="slotProps">
<div>{{ option.studiensemester_kurzbz }}</div>
</template>
</Dropdown>
</template>
</core-filter-cmpt>
</div>