Compare commits

...
Author SHA1 Message Date
adisposkofh 99445d2372 Merge branch 'bugfix-83277_menu-nodes-subnodes-behavior' of github.com:FH-Complete/FHC-Core into bugfix-83277_menu-nodes-subnodes-behavior 2026-09-14 16:49:49 +02:00
adisposkofh 0ab6e3d00d opening active sidenav menu node's submenu on page load 2026-09-14 16:44:25 +02:00
adisposkofh 385b262be8 naming change for clarity 2026-09-14 16:08:50 +02:00
adisposkofh b4206e1e48 removed redundant comments 2026-09-14 15:25:25 +02:00
adisposkofh e09555908a code formatting 2026-09-14 15:25:15 +02:00
adisposkofh 38060c60fb cleaned up repetitive code, code formatting 2026-09-14 15:14:17 +02:00
adisposkofh 40516b34a2 moved repetitive sidenav menu node style into computed property 2026-09-14 14:21:33 +02:00
adisposkofh ea97e3a192 removed redundant comment 2026-09-14 14:18:36 +02:00
adisposkofh f34481ae60 reworked sidenav menu collapse logic 2026-09-14 14:18:01 +02:00
adisposkofh fcf3cbac6c progressively indenting sidenav submenus for hierarchical clarity 2026-09-14 11:31:53 +02:00
adisposkofh 40b28b88b9 fixed issue where click on content-less sidenav menu node didn't close open submenu 2026-09-14 09:26:33 +02:00
Harald Bamberger 11f2f0ad75 Merge branch 'master' into bugfix-83277_menu-nodes-subnodes-behavior 2026-09-10 10:48:31 +02:00
Harald Bamberger 4abe6f0d24 Merge branch 'bugfix_missing-studvw-filter-clear-action-button' 2026-09-09 16:15:39 +02:00
adisposkofh 9fbbd6b855 moved studvw 'clear filter' action button into 'actions' slot in the filter component 2026-09-09 15:14:20 +02:00
adisposkofh 60b2c89c66 fixed issue with menu node collapsing instead of activating if subnode is active 2026-09-09 13:21:53 +02:00
Andreas Österreicher 59b4b2bac9 Merge branch 'feature-71639/StudVw_Noten_Renaming_Infopopup' 2026-09-09 12:40:42 +02:00
Andreas Österreicher 5f546fca2a Merge branch 'feature-82630/Infocenter_Verlinkung_StudVW' 2026-09-09 12:37:33 +02:00
Andreas Österreicher f74b885253 Abfrage von Verträgen korrigiert für Fälle wo der Monatsletzte gleichzeitig der Start des neuen Studiensemesters ist (zB 31.08.2026) damit die Abrechnung korrekt funktioniert (BFI) 2026-09-09 08:55:24 +02:00
Andreas Österreicher 3ab402adef Merge branch 'bugfix-82884_grades-export-group-names' 2026-09-08 14:43:17 +02:00
Manuela Thamer 52f24645fb update link StudVW 2026-09-08 14:23:59 +02:00
Manuela Thamer 4ac108174b rename title delete button 2026-09-08 08:29:08 +02:00
adisposkofh 6378b80d87 fixed issue where exported gradelist excel file formatted group names as numbers 2026-09-07 15:24:38 +02:00
8 changed files with 155 additions and 141 deletions
@@ -57,7 +57,7 @@
<div class="row<?php if ($lockedbyother) echo ' alert-danger' ?>">
<div class="col-lg-8">
<h3 class="page-header">
Infocenter Details: <a target="_blank" title="Studentenverwaltung" href="<?php echo site_url('/Studentenverwaltung/' . $studiensemester . '/person/' . $stammdaten->person_id) ?>"><?php echo $stammdaten->vorname.' '.$stammdaten->nachname ?> <i class="fa fa-external-link" style="font-size:small"></i></a>
Infocenter Details: <a target="_blank" title="Studentenverwaltung" href="<?php echo site_url('/studvw/stdsem/' . $studiensemester . '/person/' . $stammdaten->person_id) ?>"><?php echo $stammdaten->vorname.' '.$stammdaten->nachname ?> <i class="fa fa-external-link" style="font-size:small"></i></a>
</h3>
</div>
<div class="col-lg-4">
+1 -1
View File
@@ -351,7 +351,7 @@ if (!$berechtigung->isBerechtigt('admin')
{
$worksheet->write($lines,3,$elem->vorname);
}
$worksheet->write($lines,4,$elem->semester.$elem->verband.$elem->gruppe);
$worksheet->writeString($lines,4,$elem->semester.$elem->verband.$elem->gruppe);
$worksheet->write($lines,5,trim($elem->matrikelnr),$format_highlight);
$worksheet->write($lines,6, $note, $format_highlightright);
+1 -1
View File
@@ -121,7 +121,7 @@ class vertrag extends basis_db
JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
WHERE
vertrag_id=tbl_vertrag.vertrag_id
AND studiensemester_kurzbz in (SELECT studiensemester_kurzbz FROM public.tbl_studiensemester WHERE start>=".$this->db_add_param($datum).")
AND studiensemester_kurzbz in (SELECT studiensemester_kurzbz FROM public.tbl_studiensemester WHERE start>".$this->db_add_param($datum).")
)
)";
}
+13 -15
View File
@@ -28,6 +28,7 @@ export default {
urlMatchRankings:[],
navUserDropdown:null,
menuOpen:true,
openMenuHierarchy: [],
};
},
inject: ["isNarrow", "isMobile"],
@@ -35,7 +36,7 @@ export default {
return{
setActiveEntry: this.setActiveEntry,
addUrlCount: this.addUrlCount,
makeParentContentActive: this.makeParentContentActive,
setOpenMenuHierarchy: this.setOpenMenuHierarchy,
}
},
computed:{
@@ -82,19 +83,6 @@ export default {
handleHideNavUser(){
document.removeEventListener("click", this.checkSettingsVisibility);
},
makeParentContentActive(content_id, collection=this.entries, parent=null){
if(!collection) return;
if (typeof collection == 'object' && !Array.isArray(collection) && Object.entries(collection).length > 0) {
collection = Object.values(collection);
}
for(let entry of collection){
if(entry.content_id == content_id){
this.activeEntry = parent;
}
this.makeParentContentActive(content_id, entry.childs, entry.content_id);
}
},
addUrlCount(count){
this.urlMatchRankings.push(count);
},
@@ -102,6 +90,9 @@ export default {
setActiveEntry(content_id){
this.activeEntry = content_id;
},
setOpenMenuHierarchy(openMenuHierarchy) {
this.openMenuHierarchy = openMenuHierarchy;
},
},
created(){
this.fetchMenu();
@@ -192,7 +183,14 @@ export default {
<div class="offcanvas-body p-0">
<div id="nav-main-menu" class="nav-menu-collapse collapse collapse-horizontal show">
<div class="flex-grow-1">
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeEntry" v-for="entry in entries" :key="entry.content_id" :entry="entry" />
<cis-menu-entry
v-for="entry in entries"
:key="entry.content_id"
:highestMatchingUrlCount="highestMatchingUrlCount"
:activeContent="activeEntry"
:entry="entry"
:openMenuHierarchy="openMenuHierarchy"
/>
</div>
</div>
</div>
+90 -94
View File
@@ -8,6 +8,11 @@ export default {
},
activeContent: [String, Number],
highestMatchingUrlCount: Number,
openMenuHierarchy: Array,
precedingMenuHierarchy: {
type: Array,
default: [],
},
},
data: () => {
return {
@@ -15,7 +20,7 @@ export default {
urlCount:0,
}
},
inject: ['makeParentContentActive', 'setActiveEntry','addUrlCount'],
inject: ['setActiveEntry', 'setOpenMenuHierarchy', 'addUrlCount'],
watch:{
highestMatchingUrlCount: function(newValue)
{
@@ -25,49 +30,27 @@ export default {
this.setActiveEntry(this.entry.content_id);
}
},
activeContent: function(newValue){
if(newValue == this.entry.content_id){
// wenn der Menupunkt nicht bereits offen ist
if (!this.entry.menu_open){
this.entry.menu_open = true;
}
}else{
if (this.searchRecursiveChild(this.entry, 'content_id',newValue)) {
this.entry.menu_open = true;
} else {
this.entry.menu_open = false;
}
openMenuHierarchy() {
if (!this.hasChilds) return;
if (this.$props.openMenuHierarchy.length && this.$props.openMenuHierarchy[0] === this.$props.entry.content_id) {
this.$props.entry.menu_open = true;
} else {
this.$props.entry.menu_open = false;
}
},
'entry.menu_open': function (newValue,oldValue) {
if (newValue)
{
// only invokes .show if this.collapse is not null
this.collapse && this.collapse.show();
}
else
{
// only invokes .hide if this.collapse is not null
this.collapse && this.collapse.hide();
if (this.activeContent == this.entry.content_id)
{
this.makeParentContentActive(this.entry.content_id);
}
'entry.menu_open': function (isMenuOpen) {
if (!this.collapse)
return;
if (isMenuOpen) {
this.collapse.show();
} else {
this.collapse.hide();
}
},
},
computed: {
active: function () {
if (this.entry.menu_open){
return true;
}
else if (this.activeContent) {
return this.activeContent == this.entry.content_id;
} else {
return false;
}
},
hasFullLink() {
return this.entry.url.startsWith(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router)
},
@@ -89,25 +72,34 @@ export default {
},
hasChilds() {
return this.entry.childs && this.entry.childs.length !== 0;
}
},
menuHierarchy() {
return [...this.$props.precedingMenuHierarchy, this.$props.entry.content_id];
},
menuNodeHref() {
if (this.hasChilds) {
return this.$props.entry.menu_open && this.hasFullLink
? this.$props.entry.url
: null;
} else {
return this.$props.entry.url;
}
},
},
methods: {
getUrlMatchPoints(url,link){
let splitted_link = link.split('/');
let splitted_url = url.href.split('/');
let count = 0;
for(let part_url of splitted_url)
{
for (let part_link of splitted_link)
{
if(part_url == part_link)
{
for (let part_url of splitted_url) {
for (let part_link of splitted_link) {
if (part_url === part_link) {
count++;
}
}
}
this.urlCount = count;
this.addUrlCount(count);
},
@@ -119,14 +111,11 @@ export default {
let url_hash = url.hash;
url_hash = url_hash.replace(url_hash_spaceSymbol_regex, " ").replace(url_hash_sharpSymbol_regex,"");
// if the url hash contains the titel of the menu
// or if the url equals the link of a menu
// then set the menu active
if (url_hash == this.entry.titel || url.href == this.entry.url) {
this.setActiveEntry(this.entry.content_id);
this.setOpenMenuHierarchy(this.menuHierarchy);
}
},
// searches the childs of an entry recursively based on the value of a property
searchRecursiveChild(entry,property,value){
if (typeof entry.childs == 'object' && !Array.isArray(entry.childs) && Object.entries(entry.childs).length > 0){
entry.childs = Object.values(entry.childs);
@@ -143,16 +132,23 @@ export default {
}
return false;
},
toggleCollapse(evt) {
if (this.active)
{
this.makeParentContentActive(this.entry.content_id);
toggleCollapse() {
if (!this.$props.entry.menu_open) {
this.setOpenMenuHierarchy(this.menuHierarchy);
} else {
this.setOpenMenuHierarchy(this.precedingMenuHierarchy);
}
else
{
this.setActiveEntry(this.entry.content_id);
},
handleClickOnMenuNode() {
if (this.hasFullLink) {
this.setActiveEntry(this.$props.entry.content_id);
if (this.hasChilds) {
this.setOpenMenuHierarchy(this.menuHierarchy);
}
} else if (this.hasChilds) {
this.toggleCollapse();
}
}
},
},
mounted() {
if (this.$refs.children) {
@@ -168,41 +164,41 @@ export default {
INCLUDE
</div>
<template v-else>
<template v-if="hasChilds">
<div class="btn-group w-100">
<a :target="target"
:href="(entry.menu_open && hasFullLink) ? entry.url : null"
@click="toggleCollapse"
:class="{
'btn btn-default rounded-0 text-start': true,
['btn-level-' + level]: true,
'fw-bold':active
}">
{{ entry.titel }}
</a>
<button @click.prevent="toggleCollapse" :aria-expanded="entry.menu_open"
:class="{
'btn btn-default rounded-0 dropdown-toggle dropdown-toggle-split flex-grow-0': true,
collapsed: !entry.menu_open
}">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
</div>
<ul ref="children"
class="nav w-100 collapse">
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeContent" v-for="child in entry.childs" :key="child" :entry="child" :level="level + 1"/>
</ul>
</template>
<a v-else
:href="entry.url"
:target="target"
:class="{
'btn btn-default rounded-0 w-100 text-start': true,
['btn-level-' + level]: true,
'fw-bold':active
}"
@mouseup="setActiveEntry(entry.content_id)">
{{ entry.titel }}
</a>
<div class="btn-group w-100">
<a
@click="handleClickOnMenuNode($event)"
:href="menuNodeHref"
:target="target"
class="btn btn-default rounded-0 text-start"
:class="{
['btn-level-' + level]: true,
'fw-bold': $props.activeContent === $props.entry.content_id
}"
:style="'padding-left: calc(var(--bs-btn-padding-x) * ' + $props.level + ');'"
>
{{ entry.titel }}
</a>
<button
v-if="hasChilds"
@click.prevent="toggleCollapse()"
:aria-expanded="entry.menu_open"
class="btn btn-default rounded-0 dropdown-toggle dropdown-toggle-split flex-grow-0"
:class="{ collapsed: !entry.menu_open }"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
</div>
<ul v-if="hasChilds" ref="children" class="nav w-100 collapse" >
<cis-menu-entry
v-for="child in entry.childs"
:key="child"
:highestMatchingUrlCount="highestMatchingUrlCount"
:activeContent="activeContent"
:entry="child"
:level="level + 1"
:openMenuHierarchy="$props.openMenuHierarchy.slice(1)"
:precedingMenuHierarchy="menuHierarchy"
/>
</ul>
</template>`
};
@@ -255,7 +255,7 @@ export default {
deleteButton.className = 'btn btn-outline-secondary';
const icon = document.createElement('i');
icon.className = 'fa fa-trash';
icon.title = this.$p.t('ui/loeschen');
icon.title = this.$p.t('lehre/info_deleteMarkEntry');
deleteButton.append(icon);
deleteButton.addEventListener('click', evt => {
evt.stopPropagation();
@@ -822,18 +822,33 @@ export default {
@headerFilterOn="handleHeaderFilter"
>
<!--
<template #actions>
<div>
<button
class="btn btn-outline-success sm mb-1"
:title="'Export ' + selectedPrestudents.length + ' prestudent(s) to Excel'"
>
<i class="fas fa-file-excel fa-xl"></i>
</button>
</div>
</template>
-->
<template #actions>
<!-- <div>
<button
class="btn btn-outline-success sm mb-1"
:title="'Export ' + selectedPrestudents.length + ' prestudent(s) to Excel'"
>
<i class="fas fa-file-excel fa-xl"></i>
</button>
</div> -->
<template v-if="filter.length || headerFilterActive">
<div class="d-flex justify-content-center align-items-center gap-2 ps-4 position-absolute start-50 translate-middle-x">
<p class="text-danger mb-0">
<strong>{{$p.t('filter','filterActive')}}</strong>
</p>
<button
class="btn btn-outline-danger sm"
:title="$p.t('filter/filterDelete')"
@click="resetFilter"
>
<span class="fa-solid fa-filter-circle-xmark"></span>
</button>
</div>
</template>
</template>
<template #additional>
<div class="pe-1">
@@ -873,22 +888,7 @@ export default {
<i class="fa-solid fa-refresh pe-1"></i> {{selected.length}}
</button>
</div>
</template>
<template v-if="filter.length || headerFilterActive">
<div class="d-flex justify-content-center align-items-center gap-2 ps-4 position-absolute start-50 translate-middle-x">
<p class="text-danger mb-0">
<strong>{{$p.t('filter','filterActive')}}</strong>
</p>
<button
class="btn btn-outline-danger sm"
:title="$p.t('filter/filterDelete')"
@click="resetFilter"
>
<span class="fa-solid fa-filter-circle-xmark"></span>
</button>
</div>
</template>
<template #filter>
+20
View File
@@ -62411,6 +62411,26 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
array(
'app' => 'core',
'category' => 'lehre',
'phrase' => 'info_deleteMarkEntry',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Noteneintrag entfernen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Remove grade entry',
'description' => '',
'insertvon' => 'system'
)
)
),
);