Merge branch 'feature-63192/FHC4-Studierendenverwaltung_Semsterauswahl_fuer_mehrere_Tabs_anpassen' into rc1_FHC4_C4

This commit is contained in:
Harald Bamberger
2025-08-04 10:22:44 +02:00
3 changed files with 164 additions and 32 deletions
+34 -4
View File
@@ -48,6 +48,33 @@ const router = VueRouter.createRouter({
next();
}
},
{
name: 'studiengang',
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/:studiengang`,
component: FhcStudentenverwaltung,
props: (route) => {
return {
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
url_studiengang: route.params.studiengang,
};
},
beforeEnter: (to, from, next) => {
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
if (!isSemester) {
return next({name: 'index'});
}
const isStudiengang = /^[A-Z]{3}/.test(to.params.studiengang);
if (!isStudiengang) {
return next({
name: 'studiensemester',
params: {
studiensemester_kurzbz: to.params.studiensemester_kurzbz
}
});
}
next();
}
},
{
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id`,
component: FhcStudentenverwaltung,
@@ -131,11 +158,14 @@ const router = VueRouter.createRouter({
});
router.afterEach((to, from, failure) => {
if (to.params.studiensemester_kurzbz) {
document.title = to.params.studiensemester_kurzbz + ' - Studierendenverwaltung FH-Complete';
} else {
document.title = 'Studierendenverwaltung FH-Complete';
let title = 'Studierendenverwaltung FH-Complete';
if (to.params.studiengang) {
title = to.params.studiengang + ' ' + title;
}
if (to.params.studiensemester_kurzbz) {
title = to.params.studiensemester_kurzbz + ' ' + title;
}
document.title = title;
});
const app = Vue.createApp({
+65 -16
View File
@@ -24,6 +24,7 @@ import StvStudiensemester from "./Studentenverwaltung/Studiensemester.js";
import ApiSearchbar from "../../api/factory/searchbar.js";
import ApiStv from "../../api/factory/stv.js";
import ApiStvVerband from '../../api/factory/stv/verband.js';
export default {
@@ -42,7 +43,12 @@ export default {
permissions: Object,
stvRoot: String,
cisRoot: String,
activeAddons: String // semicolon separated list of active addons
activeAddons: String, // semicolon separated list of active addons
url_studiensemester_kurzbz: String,
url_mode: String,
url_prestudent_id: String,
url_tab: String,
url_studiengang: String
},
provide() {
return {
@@ -113,23 +119,30 @@ export default {
mergeResults: 'person'
},
studiengangKz: undefined,
studiengangKuerzel: '',
studiensemesterKurzbz: this.defaultSemester,
lists: {
nations: [],
sprachen: [],
geschlechter: []
}
},
verbandEndpoint: ApiStvVerband
}
},
watch: {
'$attrs.url_studiensemester_kurzbz': function (newVal, oldVal) {
'url_studiensemester_kurzbz': function (newVal, oldVal) {
if (newVal !== oldVal) {
this.studiensemesterKurzbz = newVal;
this.$refs.stvList.updateUrl();
this.$refs.details.reload();
}
},
'$attrs.url_mode': function () {
'url_studiengang': function (newVal, oldVal) {
if (newVal !== oldVal) {
this.checkUrlStudiengang();
}
},
'url_mode': function () {
this.handlePersonUrl();
}
},
@@ -173,16 +186,32 @@ export default {
{
urlpath = 'CURRENT_SEMESTER' + '/' + urlpath;
}
this.$refs.stvList.updateUrl(ApiStv.students.verband(urlpath));
this.studiengangKz = studiengang_kz;
this.$refs.stvList.updateUrl(
ApiStv.students.verband(urlpath)
);
this.$router.push({
name: 'studiensemester',
params: {
studiensemester_kurzbz: this.studiensemesterKurzbz
}
const stg = this.lists.stgs.find((element) => {
return (element.studiengang_kz === this.studiengangKz);
});
if (stg)
{
this.studiengangKuerzel = (stg.typ + stg.kurzbz).toUpperCase()
this.$router.push({
name: 'studiengang',
params: {
studiensemester_kurzbz: this.studiensemesterKurzbz,
studiengang: this.studiengangKuerzel
}
});
} else
{
this.studiengangKuerzel = '';
this.$router.push({
name: 'studiensemester',
params: {
studiensemester_kurzbz: this.studiensemesterKurzbz
}
});
}
},
studiensemesterChanged(v) {
this.studiensemesterKurzbz = v;
@@ -219,10 +248,29 @@ export default {
true
);
}
},
checkUrlStudiengang() {
if (this.url_studiengang) {
const stg = this.lists.stgs.find((element) => {
const kuerzel = (element.typ + element.kurzbz).toUpperCase();
return (this.url_studiengang === kuerzel);
});
if (stg) {
this.studiengangKz = stg.studiengang_kz;
this.studiengangKuerzel = (stg.typ + stg.kurzbz).toUpperCase();
} else {
this.$router.replace({
name: 'studiensemester',
params: {
studiensemester_kurzbz: this.studiensemesterKurzbz
}
});
}
}
}
},
created() {
if (!this.$attrs?.url_studiensemester_kurzbz) {
if (!this.url_studiensemester_kurzbz) {
this.$router.replace({
name: 'studiensemester',
params: {
@@ -230,7 +278,7 @@ export default {
}
});
} else {
this.studiensemesterKurzbz = this.$attrs.url_studiensemester_kurzbz;
this.studiensemesterKurzbz = this.url_studiensemester_kurzbz;
}
this.$api
@@ -266,6 +314,7 @@ export default {
.then(result => {
this.lists.stgs = result.data;
this.lists.active_stgs = this.lists.stgs.filter(stg => stg.aktiv);
this.checkUrlStudiengang();
})
.catch(this.$fhcAlert.handleSystemError);
@@ -299,7 +348,7 @@ export default {
template: `
<div class="stv">
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-4 col-lg-3 col-xl-2 me-0 px-3" :href="stvRoot">FHC 4.0</a>
<a class="navbar-brand col-md-4 col-lg-3 col-xl-2 me-0 px-3" :href="stvRoot">StudVw: {{studiensemesterKurzbz}} {{studiengangKuerzel}}</a>
<button class="navbar-toggler d-md-none m-1 collapsed" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" :aria-label="$p.t('ui/toggle_nav')"><span class="navbar-toggler-icon"></span></button>
<core-searchbar
:searchoptions="searchbaroptions"
@@ -313,7 +362,7 @@ export default {
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<stv-verband @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
<stv-verband :preselectedKey="'' + studiengangKz" :endpoint="verbandEndpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
<stv-studiensemester v-model:studiensemester-kurzbz="studiensemesterKurzbz" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
</nav>
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
@@ -5,8 +5,6 @@ import PvTree from "../../../../../index.ci.php/public/js/components/primevue/tr
import PvTreetable from "../../../../../index.ci.php/public/js/components/primevue/treetable/treetable.esm.min.js";
import PvColumn from "../../../../../index.ci.php/public/js/components/primevue/column/column.esm.min.js";
import ApiStvVerband from '../../../api/factory/stv/verband.js';
export default {
components: {
PvTree,
@@ -16,11 +14,22 @@ export default {
emits: [
'selectVerband'
],
props: {
endpoint: {
type: Object,
required: true,
},
preselectedKey: {
type: String,
default: null
}
},
data() {
return {
loading: true,
nodes: [],
selectedKey: [],
expandedKeys: {},
filters: {}, // TODO(chris): filter only 1st level?
favnodes: [],
favorites: {on: false, list: []}
@@ -32,6 +41,13 @@ export default {
return this.favorites.on ? this.favnodes : this.nodes;
}
},
watch: {
'preselectedKey': function (newVal, oldVal) {
if (newVal !== oldVal) {
this.setPreselection();
}
}
},
methods: {
findNodeByKey(key, arr) {
if (!arr)
@@ -44,7 +60,7 @@ export default {
return res.pop();
return null;
},
onExpandTreeNode(node) {
async onExpandTreeNode(node) {
if (!node.children) {
if (node.data.link) {
let activeEl = null;
@@ -55,8 +71,8 @@ export default {
});
this.loading = true;
this.$api
.call(ApiStvVerband.get(node.data.link))
return this.$api
.call(this.endpoint.get(node.data.link))
.then(result => result.data)
.then(result => {
const subNodes = result.map(this.mapResultToTreeData);
@@ -68,7 +84,7 @@ export default {
let treeitem = this.$refs.tree.$el.querySelector('[data-tree-item-key="' + node.key + '"]');
treeitem = treeitem.closest('[role="row"]');
this.$nextTick(() => {
if (activeEl == document.activeElement)
treeitem.dispatchEvent(new KeyboardEvent('keydown', {
@@ -108,7 +124,7 @@ export default {
}
this.favorites.on = !this.favorites.on;
this.$api
.call(ApiStvVerband.favorites.set(
.call(this.endpoint.favorites.set(
JSON.stringify(this.favorites)
));
this.loading = false;
@@ -135,7 +151,7 @@ export default {
for (let parent in sortedInParents)
promises.push(
this.$api
.call(ApiStvVerband.get(parent == '_' ? '' : parent))
.call(this.endpoint.get(parent == '_' ? '' : parent))
.then(res => res.data)
.then(res => res.filter(node => sortedInParents[parent].includes(node.link + '')))
);
@@ -160,9 +176,9 @@ export default {
this.favnodes.push((await this.loadNodes([key.data.link])).pop());
this.favorites.list.push(key.data.link + '');
}
this.$api
.call(ApiStvVerband.favorites.set(
.call(this.endpoint.favorites.set(
JSON.stringify(this.favorites)
));
},
@@ -181,19 +197,55 @@ export default {
let items = e.target.querySelectorAll('[data-link-fav-add][tabindex="-1"]');
items.forEach(el => el.tabIndex = 0);
}
},
async setPreselection()
{
if (!this.preselectedKey)
return;
let rawKey = this.preselectedKey
if (!rawKey || typeof rawKey !== 'string')
return;
const parts = this.preselectedKey.split('/');
let currentKey = parts[0];
let currentNode = this.findNodeByKey(currentKey);
if (!currentNode)
return;
for (let i = 1; i < parts.length; i++)
{
this.expandedKeys[currentNode.key] = true;
await this.onExpandTreeNode(currentNode);
currentKey += '-' + parts[i];
currentNode = this.findNodeByKey(currentKey);
if (!currentNode)
{
return;
}
}
this.selectedKey = {[currentNode.key]: true};
this.onSelectTreeNode(currentNode);
}
},
mounted() {
this.$api
.call(ApiStvVerband.get())
.call(this.endpoint.get())
.then(result => {
this.nodes = result.data.map(this.mapResultToTreeData);
this.setPreselection();
this.loading = false;
})
.catch(this.$fhcAlert.handleSystemError);
this.$api
.call(ApiStvVerband.favorites.get())
.call(this.endpoint.favorites.get())
.then(result => {
if (result.data) {
let f = JSON.parse(result.data);
@@ -218,6 +270,7 @@ export default {
:value="filteredNodes"
@node-expand="onExpandTreeNode"
selection-mode="single"
v-model:expanded-keys="expandedKeys"
v-model:selection-keys="selectedKey"
@node-select="onSelectTreeNode"
scrollable