mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-12 06:54:04 +00:00
Merge branch 'master' into feature-77757/WidgetsBerechtigung
This commit is contained in:
@@ -38,3 +38,9 @@ textarea[name="anmerkung"] {
|
||||
{
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
.node-verband {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@import './SvgIcons.css';
|
||||
@import './components/searchbar/searchbar.css';
|
||||
@import './components/verticalsplit.css';
|
||||
@import './components/horizontalsplit.css';
|
||||
@import './components/FilterComponent.css';
|
||||
@import './components/Tabs.css';
|
||||
@import './components/Notiz.css';
|
||||
@@ -95,6 +96,13 @@ html.fs_huge {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* to enable scrolling for many student entries*/
|
||||
.stv-details-abschlusspruefung-student-names {
|
||||
max-height: 80px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.p-dropdown-item{
|
||||
z-index: 1201 !important;
|
||||
}
|
||||
|
||||
@@ -62,3 +62,16 @@
|
||||
top:13%;"
|
||||
z-index: 104; font-size: 1rem; width: 2.5rem; height: 2.5rem; opacity:0; transition: opacity 0.2s; top:13%;"
|
||||
}*/
|
||||
|
||||
.tag-hover:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.btn-hover {
|
||||
--bs-btn-hover-bg: #e9ecef; /* Neue Hintergrundfarbe beim Hover */
|
||||
--bs-btn-hover-border-color: #e9ecef; /* Neue Rahmenfarbe beim Hover */
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.filter-table-dataset .tabulator-tableholder {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.tabulator {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
:root {
|
||||
--fhc-horizontalsplit-hsplitter-bg-color: var(--fhc-background, #eee);
|
||||
--fhc-horizontalsplit-hsplitter-border-color: var(--fhc-border, #eee);
|
||||
--fhc-horizontalsplit-hsplitter-splitactions-color: var(--fhc-dark, #000);
|
||||
}
|
||||
|
||||
.horizontalsplit-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.horizontalsplitted {
|
||||
overflow: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.horizontalsplitter {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.horizontalsplitter.left {
|
||||
border-left: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color);
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.horizontalsplitter.right {
|
||||
border-right: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color);
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.splitactions.horizontal {
|
||||
background-color: var(--fhc-horizontalsplit-hsplitter-bg-color);
|
||||
color: var(--fhc-horizontalsplit-hsplitter-splitactions-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px 0 5px 0;
|
||||
}
|
||||
|
||||
.splitactions.horizontal.left {
|
||||
border-radius: 0 40% 40% 0;
|
||||
}
|
||||
|
||||
.splitactions.horizontal.right {
|
||||
border-radius: 40% 0 0 40%;
|
||||
}
|
||||
|
||||
.splitactions.horizontal .splitaction {
|
||||
width: 14px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.splitactions.horizontal .splitaction.resize {
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
#content > div:first-child {
|
||||
margin-top: 30px;
|
||||
}
|
||||
@@ -107,4 +107,9 @@
|
||||
|
||||
.gesperrtoption {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
#onboardingRow
|
||||
{
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
+40
-2
@@ -46,7 +46,6 @@
|
||||
background-color: #ffA500ff;
|
||||
}
|
||||
|
||||
|
||||
.tag_braun {
|
||||
background-color: #6d4c41;
|
||||
}
|
||||
@@ -69,12 +68,46 @@
|
||||
|
||||
.tag_limette {
|
||||
background-color: #D3FFCE;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.tag_turquoise {
|
||||
background-color: #89cccf;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.tag_bordeaux {
|
||||
background-color: #7B1B38;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag_olive {
|
||||
background-color: #868d07;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag_beige {
|
||||
background-color: #E4D5B4;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.tag_skyblue {
|
||||
background-color: #90D5FF;
|
||||
color: black;
|
||||
}
|
||||
.tag_blush {
|
||||
background-color: #A83E3E;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag_done {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.tag_auto {
|
||||
border: 1px dashed white;
|
||||
}
|
||||
|
||||
.display_all {
|
||||
background-color: darkgrey !important;
|
||||
border: none;
|
||||
@@ -111,4 +144,9 @@
|
||||
.copy-btn {
|
||||
float: right;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-hover {
|
||||
--bs-btn-hover-bg: #e9ecef;
|
||||
--bs-btn-hover-border-color: #e9ecef;
|
||||
}
|
||||
|
||||
@@ -34,4 +34,10 @@ export default {
|
||||
url: 'api/frontend/v1/detailheader/detailheader/getLeitungOrg/' + oekurzbz,
|
||||
};
|
||||
},
|
||||
getSemesterStati(prestudent_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/detailheader/detailheader/getSemesterStati/' + prestudent_id,
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -29,5 +29,20 @@ export default {
|
||||
url: 'api/frontend/v1/organisation/studiensemester/getAll',
|
||||
params: { order, start }
|
||||
};
|
||||
},
|
||||
current() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/organisation/studiensemester/current'
|
||||
};
|
||||
},
|
||||
set(studiensemester_kurzbz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/organisation/studiensemester/set',
|
||||
params: {
|
||||
studiensemester_kurzbz
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
import app from './stv/app.js';
|
||||
import lists from './stv/lists.js';
|
||||
import verband from './stv/verband.js';
|
||||
import students from './stv/students.js';
|
||||
import filter from './stv/filter.js';
|
||||
import konto from './stv/konto.js';
|
||||
@@ -34,7 +33,6 @@ import admissionDates from './stv/admissionDates.js';
|
||||
export default {
|
||||
app,
|
||||
lists,
|
||||
verband,
|
||||
students,
|
||||
filter,
|
||||
konto,
|
||||
|
||||
@@ -16,16 +16,20 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
getCourselist(params) {
|
||||
getCourselist(student_uid, start_date, end_date, stundenplan) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/LvTermine/getStundenplan/' + params.student_uid + '/'
|
||||
+ params.start_date + '/'
|
||||
+ params.end_date + '/'
|
||||
+ params.group_consecutiveHours + '/'
|
||||
+ params.dbStundenplanTable
|
||||
url: 'api/frontend/v1/stv/LvTermine/getStundenplan/' + encodeURIComponent(student_uid) + '/'
|
||||
+ encodeURIComponent(start_date) + '/'
|
||||
+ encodeURIComponent(end_date) + '/'
|
||||
+ encodeURIComponent(stundenplan) + '/'
|
||||
+ encodeURIComponent(true)
|
||||
};
|
||||
},
|
||||
exportCalendar(student_uid, stundenplan)
|
||||
{
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'content/statistik/termine.xls.php?student_uid=' + encodeURIComponent(student_uid) + '&db_stpl_table='+encodeURIComponent(stundenplan);
|
||||
},
|
||||
getStudiensemester(){
|
||||
return {
|
||||
method: 'get',
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
export default {
|
||||
uid(uid, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
let url = 'api/frontend/v1/treemenudata/stv/stdsem/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/uid/'
|
||||
+ '/student/'
|
||||
+ encodeURIComponent(uid);
|
||||
return {
|
||||
method: 'get',
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
};
|
||||
},
|
||||
prestudent(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
let url = 'api/frontend/v1/treemenudata/stv/stdsem/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/prestudent/'
|
||||
+ encodeURIComponent(prestudent_id);
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
};
|
||||
},
|
||||
person(person_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
let url = 'api/frontend/v1/treemenudata/stv/stdsem/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/person/'
|
||||
+ encodeURIComponent(person_id);
|
||||
|
||||
@@ -51,4 +51,20 @@ export default {
|
||||
params: data
|
||||
};
|
||||
},
|
||||
|
||||
getAllTagsPrestudent(prestudent_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/Tags/getAllTags',
|
||||
params: prestudent_id
|
||||
};
|
||||
},
|
||||
|
||||
rebuildTagsforTypeId(data){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/Tags/rebuildTagsForTypeId/',
|
||||
params: data
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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 {
|
||||
getTabulatorPresets(tableName) {
|
||||
return {
|
||||
method: "get",
|
||||
url:
|
||||
"/api/frontend/v1/TabulatorPresets/getTabulatorPresets?tableName=" +
|
||||
tableName,
|
||||
};
|
||||
},
|
||||
createTabulatorPreset(tableName, presetName, preset) {
|
||||
return {
|
||||
method: "post",
|
||||
url: "/api/frontend/v1/TabulatorPresets/createTabulatorPreset",
|
||||
params: {
|
||||
tableName,
|
||||
presetName,
|
||||
preset,
|
||||
},
|
||||
};
|
||||
},
|
||||
updateTabulatorPreset(presetId, preset) {
|
||||
return {
|
||||
method: "post",
|
||||
url: "/api/frontend/v1/TabulatorPresets/updateTabulatorPreset",
|
||||
params: {
|
||||
presetId,
|
||||
preset,
|
||||
},
|
||||
};
|
||||
},
|
||||
deleteTabulatorPreset(tabulatorPresetId) {
|
||||
return {
|
||||
method: "post",
|
||||
url:
|
||||
"/api/frontend/v1/TabulatorPresets/deleteTabulatorPreset",
|
||||
params: {
|
||||
presetId: tabulatorPresetId,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
* Copyright (C) 2026 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
|
||||
@@ -16,26 +16,29 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
get(path) {
|
||||
let url = 'api/frontend/v1/stv/verband';
|
||||
if (path)
|
||||
url += '/' + path;
|
||||
get(config, path = '') {
|
||||
return {
|
||||
method: 'get',
|
||||
url
|
||||
url: '/api/frontend/v1/treemenu/' + config + '/' + path
|
||||
};
|
||||
},
|
||||
data(config, path = '') {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/treemenudata/' + config + '/' + path
|
||||
};
|
||||
},
|
||||
favorites: {
|
||||
get() {
|
||||
get(config) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/favorites'
|
||||
url: 'api/frontend/v1/favorites/get/' + config
|
||||
};
|
||||
},
|
||||
set(favorites) {
|
||||
set(config, favorites) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/favorites/set',
|
||||
url: 'api/frontend/v1/favorites/set/' + config,
|
||||
params: { favorites }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
get() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/lv/config/get'
|
||||
};
|
||||
},
|
||||
set(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/lv/config/set',
|
||||
params
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -18,6 +18,14 @@ export default {
|
||||
params: newData
|
||||
};
|
||||
},
|
||||
getGruppe(gruppe)
|
||||
{
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/lv/gruppe/getGruppe/',
|
||||
params: gruppe
|
||||
};
|
||||
},
|
||||
getByLehreinheit(lehreinheit_id)
|
||||
{
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
getCourselist(le_id, start_date, end_date, stundenplan)
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/lvPlan/getLeEvents/' + encodeURIComponent(le_id) + "/" + encodeURIComponent(start_date) + "/" + encodeURIComponent(end_date) + "/" + encodeURIComponent(stundenplan),
|
||||
};
|
||||
},
|
||||
exportCalendar(le_id, stundenplan)
|
||||
{
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'content/statistik/termine.xls.php?lehreinheit_id=' + encodeURIComponent(le_id) + '&db_stpl_table='+encodeURIComponent(stundenplan);
|
||||
},
|
||||
getStudiensemester(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/lv/setup/getStudiensemester/'
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
getCourselist(lv_id, start_date, end_date, stundenplan)
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/lvPlan/getLvEvents/' + encodeURIComponent(lv_id) + "/" + encodeURIComponent(start_date) + "/" + encodeURIComponent(end_date) + "/" + encodeURIComponent(stundenplan),
|
||||
};
|
||||
},
|
||||
exportCalendar(lv_id, stundenplan)
|
||||
{
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'content/statistik/termine.xls.php?lehrveranstaltung_id=' + encodeURIComponent(lv_id) + '&db_stpl_table='+encodeURIComponent(stundenplan);
|
||||
},
|
||||
getStudiensemester(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/lv/setup/getStudiensemester/'
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import Grades from '../factory/stv/grades.js';
|
||||
|
||||
export default {
|
||||
...Grades,
|
||||
getCertificate(lv_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/lv/noten/getCertificate/' + encodeURIComponent(lv_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: url
|
||||
};
|
||||
},
|
||||
getTeacherProposal(lv_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/lv/noten/getTeacherProposal/' + encodeURIComponent(lv_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: url
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,9 +1,16 @@
|
||||
export default {
|
||||
getTabs()
|
||||
getLETabs()
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/lv/setup/getTabs/'
|
||||
url: '/api/frontend/v1/lv/setup/getLETabs/'
|
||||
};
|
||||
},
|
||||
getLVTabs()
|
||||
{
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/lv/setup/getLVTabs/'
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* 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 {
|
||||
get(path) {
|
||||
let url = 'api/frontend/v1/lv/StgTree';
|
||||
if (path)
|
||||
url += '/' + path;
|
||||
return {
|
||||
method: 'get',
|
||||
url
|
||||
};
|
||||
},
|
||||
favorites: {
|
||||
get() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/lv/favorites'
|
||||
};
|
||||
},
|
||||
set(favorites) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/lv/favorites/set',
|
||||
params: { favorites }
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import verband from './stv/verband.js';
|
||||
import students from './stv/students.js';
|
||||
import filter from './stv/filter.js';
|
||||
import konto from './stv/konto.js';
|
||||
@@ -18,7 +17,6 @@ import jointstudies from "./stv/jointstudies.js";
|
||||
import courselist from './stv/courselist.js';
|
||||
|
||||
export default {
|
||||
verband,
|
||||
students,
|
||||
filter,
|
||||
konto,
|
||||
|
||||
@@ -35,9 +35,6 @@ export default {
|
||||
getMitarbeiter(searchString){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/abschlusspruefung/getMitarbeiter/' + searchString);
|
||||
},
|
||||
getPruefer(searchString){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/abschlusspruefung/getPruefer/' + searchString);
|
||||
},
|
||||
getNoten(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/abschlusspruefung/getNoten/');
|
||||
},
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export default {
|
||||
get() {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/verband');
|
||||
},
|
||||
favorites: {
|
||||
get() {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/favorites');
|
||||
},
|
||||
set(favorites) {
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/favorites/set', {
|
||||
favorites
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.j
|
||||
import Studium from "../../components/Cis/Studium/Studium.js";
|
||||
import StgOrgLvPlan from "../../components/Cis/LvPlan/StgOrg.js";
|
||||
import OtherLvPlan from "../../components/Cis/LvPlan/OtherLvPlan.js";
|
||||
import Compat from "../../components/Cis/Compat.js";
|
||||
|
||||
import ApiRouteInfo from '../../api/factory/routeinfo.js';
|
||||
import {capitalize} from "../../helpers/StringHelpers.js";
|
||||
@@ -29,6 +30,18 @@ const isMobile = window.matchMedia("(max-width: 767px)").matches;
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(`/${ciPath}`),
|
||||
routes: [
|
||||
{
|
||||
path: `/Cis/Compat/:mode(ci|legacy)/:path(.*)`,
|
||||
name: 'Compat',
|
||||
component: Compat,
|
||||
props: (route) => {
|
||||
return {
|
||||
mode: route.params.mode,
|
||||
path: route.params.path,
|
||||
query_string: VueRouter.stringifyQuery(route.query)
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/Cis/Studium`,
|
||||
name: 'Studium',
|
||||
@@ -277,7 +290,7 @@ const app = Vue.createApp({
|
||||
if (target && this.isInternalRoute(target.href)) {
|
||||
const url = new URL(target.href)
|
||||
|
||||
const path = url.pathname
|
||||
const path = url.pathname + url.search;
|
||||
const base = this.$router.options.history.base
|
||||
const route = path.replace(base, '') || '/'
|
||||
|
||||
|
||||
@@ -12,74 +12,13 @@ const router = VueRouter.createRouter({
|
||||
component: LVVerwaltung
|
||||
},
|
||||
{
|
||||
name: `byEmp`,
|
||||
path: `/emp/:studiensemester_kurzbz/:emp/:stg?/:semester?`,
|
||||
name: 'stdsem',
|
||||
path: '/stdsem/:stdsem',
|
||||
component: LVVerwaltung,
|
||||
props: route => {
|
||||
let {emp, stg, semester, studiensemester_kurzbz} = route.params;
|
||||
|
||||
if (emp === '')
|
||||
emp = undefined;
|
||||
|
||||
if (stg === '')
|
||||
stg = undefined;
|
||||
|
||||
if (studiensemester_kurzbz === '')
|
||||
studiensemester_kurzbz = undefined;
|
||||
|
||||
return {
|
||||
studiensemester_kurzbz: studiensemester_kurzbz,
|
||||
emp: emp,
|
||||
stg: stg,
|
||||
semester: semester
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const { studiensemester_kurzbz } = to.params;
|
||||
const isSemester = /^(SS|WS)\d{4}$/.test(studiensemester_kurzbz);
|
||||
|
||||
if (!isSemester)
|
||||
return next({ path: '/' });
|
||||
else
|
||||
next();
|
||||
}
|
||||
},
|
||||
/*{
|
||||
name: `byFachbereich`,
|
||||
path: `/fachbereich/:fachbereich/:emp?`,
|
||||
component: LVVerwaltung
|
||||
},*/
|
||||
{
|
||||
name: `byStg`,
|
||||
path: '/stg/:studiensemester_kurzbz/:stg?/:semester?/',
|
||||
component: LVVerwaltung,
|
||||
props: route => {
|
||||
let { studiensemester_kurzbz, stg, semester } = route.params;
|
||||
|
||||
if (semester === '')
|
||||
semester = undefined;
|
||||
|
||||
if (studiensemester_kurzbz === '')
|
||||
studiensemester_kurzbz = undefined;
|
||||
|
||||
if (stg === '')
|
||||
stg = undefined;
|
||||
|
||||
return {
|
||||
studiensemester_kurzbz: studiensemester_kurzbz,
|
||||
stg: stg,
|
||||
semester: semester,
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const studiensemester_kurzbz = to.params?.studiensemester_kurzbz
|
||||
const isSemester = /^(SS|WS)\d{4}$/.test(studiensemester_kurzbz);
|
||||
|
||||
if (!isSemester)
|
||||
return next({ path: '/' });
|
||||
else
|
||||
next();
|
||||
}
|
||||
children: [
|
||||
{ name: 'emp', path: 'emp/:emp/:treemenu(.*)*', component: LVVerwaltung },
|
||||
{ name: 'treemenu', path: ':treemenu(.*)*', component: LVVerwaltung }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
@@ -91,7 +30,9 @@ const router = VueRouter.createRouter({
|
||||
|
||||
FhcApps.router.makeExtendable(router);
|
||||
|
||||
const app = Vue.createApp();
|
||||
const app = Vue.createApp({
|
||||
name: 'LvVwApp'
|
||||
});
|
||||
|
||||
FhcApps.makeExtendable(app);
|
||||
|
||||
|
||||
@@ -24,167 +24,27 @@ import PluginsPhrasen from "../plugins/Phrasen.js";
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(),
|
||||
history: VueRouter.createWebHistory('/' + ciPath),
|
||||
routes: [
|
||||
{
|
||||
name: 'index',
|
||||
path: `/${ciPath}/studentenverwaltung`,
|
||||
component: FhcStudentenverwaltung
|
||||
},
|
||||
{
|
||||
name: 'studiensemester',
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz`,
|
||||
path: '/studvw',
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
children: [
|
||||
{
|
||||
name: 'stdsem',
|
||||
path: 'stdsem/:stdsem',
|
||||
component: FhcStudentenverwaltung,
|
||||
children: [
|
||||
{ name: 'search', path: 'search/:query', component: FhcStudentenverwaltung },
|
||||
{ name: 'searchtypes', path: 'search/:types/:query', component: FhcStudentenverwaltung },
|
||||
{ name: 'prestudent', path: 'prestudent/:prestudent_id', component: FhcStudentenverwaltung },
|
||||
{ name: 'student', path: 'student/:student_uid', component: FhcStudentenverwaltung },
|
||||
{ name: 'person', path: 'person/:person_id', component: FhcStudentenverwaltung },
|
||||
{ name: 'treemenu', path: ':treemenu(.*)*', component: FhcStudentenverwaltung },
|
||||
]
|
||||
}
|
||||
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,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'prestudent',
|
||||
url_prestudent_id: route.params.prestudent_id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id/:tab`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'prestudent',
|
||||
url_prestudent_id: route.params.prestudent_id,
|
||||
url_tab: route.params.tab
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/student/:id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'student',
|
||||
url_student_id: route.params.id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/person/:person_id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'person',
|
||||
url_prestudent_id: route.params.person_id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:searchstr`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props(route) {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'search',
|
||||
url_prestudent_id: route.params.searchstr
|
||||
};
|
||||
},
|
||||
beforeEnter(to, from, next) {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_w_types',
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:types/:searchstr`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props(route) {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'search',
|
||||
url_prestudent_id: route.params.type + '/' + route.params.searchstr
|
||||
};
|
||||
},
|
||||
beforeEnter(to, from, next) {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
@@ -197,11 +57,16 @@ const router = VueRouter.createRouter({
|
||||
|
||||
router.afterEach((to, from, failure) => {
|
||||
let title = 'Studierendenverwaltung FH-Complete';
|
||||
if (to.params.studiengang) {
|
||||
title = to.params.studiengang + ' ' + title;
|
||||
if (to.params.treemenu) {
|
||||
const index = to.params.treemenu.findIndex(
|
||||
(e, i) => i%2 == 0 && e == 'stg'
|
||||
);
|
||||
if (index >= 0) {
|
||||
title = to.params.treemenu[index + 1].toUpperCase() + ' ' + title;
|
||||
}
|
||||
}
|
||||
if (to.params.studiensemester_kurzbz) {
|
||||
title = to.params.studiensemester_kurzbz + ' ' + title;
|
||||
if (to.params.stdsem) {
|
||||
title = to.params.stdsem.toUpperCase() + ' ' + title;
|
||||
}
|
||||
document.title = title;
|
||||
});
|
||||
@@ -209,7 +74,7 @@ router.afterEach((to, from, failure) => {
|
||||
FhcApps.router.makeExtendable(router);
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'StudentenverwaltungApp'
|
||||
name: 'StudVwApp'
|
||||
});
|
||||
|
||||
FhcApps.makeExtendable(app);
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
import TreemenuEntry from './Treemenu/Entry.js';
|
||||
|
||||
import dragClick from '../../directives/dragClick.js';
|
||||
|
||||
import ApiTreemenu from '../../api/factory/treemenu.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PvTreetable: primevue.treetable,
|
||||
PvColumn: primevue.column,
|
||||
TreemenuEntry
|
||||
},
|
||||
directives: {
|
||||
dragClick
|
||||
},
|
||||
emits: [
|
||||
'selectEntry',
|
||||
'drop'
|
||||
],
|
||||
props: {
|
||||
config: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
preselectedKey: {
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
nodes: [],
|
||||
selectedKey: [],
|
||||
expandedKeys: {},
|
||||
filters: {}, // TODO(chris): filter only 1st level?
|
||||
favorites: {on: false, list: []}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredNodes() {
|
||||
if (this.favorites.on)
|
||||
return this.nodes.filter(node => this.favorites.list.includes(node.data.path));
|
||||
|
||||
return this.nodes;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
preselectedKey(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.setPreselection();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reloadNodesWithProp(prop, nodes = undefined) {
|
||||
if (!nodes)
|
||||
nodes = this.nodes;
|
||||
|
||||
nodes.forEach(node => {
|
||||
if (node.data[prop]) {
|
||||
// reload
|
||||
delete node.children;
|
||||
this.onExpandTreeNode(node);
|
||||
} else if (node.children) {
|
||||
this.reloadNodesWithProp(prop, node.children);
|
||||
}
|
||||
});
|
||||
},
|
||||
findNodeByKey(key, arr) {
|
||||
if (!arr)
|
||||
arr = this.nodes;
|
||||
let res = arr.filter(n => n.key == key);
|
||||
if (res.length)
|
||||
return res.pop();
|
||||
res = arr.map(n => n.children ? this.findNodeByKey(key, n.children) : null).filter(a => a);
|
||||
if (res.length)
|
||||
return res.pop();
|
||||
return null;
|
||||
},
|
||||
async onExpandTreeNode(node) {
|
||||
if (!node.children) {
|
||||
if (node.data.path) {
|
||||
/**
|
||||
* NOTE(chris): activeEl is for keyboard navigation to
|
||||
* prevent the focus jumping down to the next parent
|
||||
* instead of the current submenu entry (which is not yet
|
||||
* loaded)
|
||||
*/
|
||||
let activeEl = null;
|
||||
this.$nextTick(() => {
|
||||
this.$nextTick(() => {
|
||||
activeEl = document.activeElement;
|
||||
});
|
||||
});
|
||||
this.loading = true;
|
||||
|
||||
return this.$api
|
||||
.call(ApiTreemenu.get(this.config, node.data.path))
|
||||
.then(result => {
|
||||
const subNodes = result.data.map(this.mapResultToTreeData);
|
||||
const realNode = this.findNodeByKey(node.key);
|
||||
if (realNode)
|
||||
realNode.children = subNodes;
|
||||
else
|
||||
node.children = subNodes; // NOTE(chris): fallback should never be the case
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (activeEl != document.activeElement)
|
||||
return;
|
||||
|
||||
let treeitem = this.$refs.tree.$el.querySelector('[data-tree-item-key="' + node.key + '"]');
|
||||
if (!treeitem)
|
||||
return;
|
||||
|
||||
treeitem = treeitem.closest('[role="row"]');
|
||||
|
||||
if (!treeitem)
|
||||
return;
|
||||
|
||||
treeitem.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
code: 'ArrowDown',
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
}
|
||||
}
|
||||
},
|
||||
onSelectTreeNode(node) {
|
||||
this.$emit('selectEntry', node.data);
|
||||
},
|
||||
mapNodesToNoSemReloadNodes(result, node) {
|
||||
if (node.data.no_sem_reload)
|
||||
result.push(node);
|
||||
if (node.children)
|
||||
result = node.children.reduce(this.mapNodesToNoSemReloadNodes, result);
|
||||
return result;
|
||||
},
|
||||
mapResultToTreeData(el) {
|
||||
const cp = {
|
||||
key: ("" + el.path).replace(/\//g, '-'),
|
||||
data: el,
|
||||
label: el.name // TODO(chris): phrase
|
||||
};
|
||||
|
||||
if (el.children)
|
||||
cp.children = el.children.map(this.mapResultToTreeData);
|
||||
else
|
||||
cp.leaf = el.leaf || false;
|
||||
|
||||
return cp;
|
||||
},
|
||||
async setPreselection()
|
||||
{
|
||||
if (!this.preselectedKey || !this.preselectedKey.length)
|
||||
{
|
||||
this.selectedKey = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.preselectedKey || !Array.isArray(this.preselectedKey))
|
||||
return;
|
||||
|
||||
// [1, 2, 3, 4, ...] => ['1-2', '3-4', ...]
|
||||
const parts = this.preselectedKey.reduce((r, c, i, a) => (i%2 && r.push(a[i-1] + '-' + c), r), []);
|
||||
let currentKey = parts[0];
|
||||
let currentNode = this.findNodeByKey(currentKey);
|
||||
|
||||
if (!currentNode)
|
||||
return;
|
||||
|
||||
if(this.selectedKey)
|
||||
{
|
||||
const currentSelectedKey = Object.keys(this.selectedKey).find(Boolean);
|
||||
if (currentSelectedKey) {
|
||||
if (currentSelectedKey == currentKey)
|
||||
return;
|
||||
/**
|
||||
* Do not select a new entry if the current is a child of the new one.
|
||||
* This happens if a child entry of a new stg is selected and the router
|
||||
* tries to select the stg root entry (because subtrees do not have
|
||||
* routes yet)
|
||||
*/
|
||||
const isChild = this.findNodeByKey(
|
||||
currentSelectedKey,
|
||||
currentNode.children || []
|
||||
);
|
||||
if (isChild)
|
||||
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);
|
||||
},
|
||||
async toggleTreeNode(node) {
|
||||
if (this.expandedKeys[node.key]) {
|
||||
delete this.expandedKeys[node.key];
|
||||
} else if (!node.leaf) {
|
||||
await this.onExpandTreeNode(node);
|
||||
this.expandedKeys[node.key] = true;
|
||||
}
|
||||
},
|
||||
filterFav() {
|
||||
this.favorites.on = !this.favorites.on;
|
||||
this.$api
|
||||
.call(ApiTreemenu.favorites.set(
|
||||
this.config,
|
||||
JSON.stringify(this.favorites)
|
||||
));
|
||||
},
|
||||
markFav(key) {
|
||||
let index = this.favorites.list.indexOf(key.data.path + '');
|
||||
|
||||
if (index != -1) {
|
||||
this.favorites.list.splice(index, 1);
|
||||
} else {
|
||||
this.favorites.list.push(key.data.path + '');
|
||||
}
|
||||
|
||||
this.$api
|
||||
.call(ApiTreemenu.favorites.set(
|
||||
this.config,
|
||||
JSON.stringify(this.favorites)
|
||||
));
|
||||
},
|
||||
unsetFavFocus(e) {
|
||||
if (e.target.dataset?.linkFavAdd !== undefined) {
|
||||
e.target.tabIndex = -1;
|
||||
} else {
|
||||
let items = e.target.querySelectorAll('[data-link-fav-add]:not([tabindex="-1"])');
|
||||
items.forEach(el => el.tabIndex = document.activeElement == el ? 0 : -1);
|
||||
}
|
||||
},
|
||||
setFavFocus(e) {
|
||||
if (e.target.dataset?.linkFavAdd !== undefined) {
|
||||
e.target.tabIndex = 0;
|
||||
} else {
|
||||
let items = e.target.querySelectorAll('[data-link-fav-add][tabindex="-1"]');
|
||||
items.forEach(el => el.tabIndex = 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$api
|
||||
.call(ApiTreemenu.get(this.config))
|
||||
.then(result => {
|
||||
this.nodes = result.data.map(el => {
|
||||
el.root = true;
|
||||
return this.mapResultToTreeData(el);
|
||||
});
|
||||
this.setPreselection();
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
this.$api
|
||||
.call(ApiTreemenu.favorites.get(this.config))
|
||||
.then(result => {
|
||||
if (result.data) {
|
||||
this.favorites = JSON.parse(result.data);
|
||||
}
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
template: /* html */`
|
||||
<pv-treetable
|
||||
ref="tree"
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
v-model:selection-keys="selectedKey"
|
||||
class="menu p-treetable-sm"
|
||||
:value="filteredNodes"
|
||||
selection-mode="single"
|
||||
scrollable
|
||||
scroll-height="flex"
|
||||
:filters="filters"
|
||||
@node-expand="onExpandTreeNode"
|
||||
@node-select="onSelectTreeNode"
|
||||
@focusin="setFavFocus"
|
||||
@focusout="unsetFavFocus"
|
||||
>
|
||||
<pv-column
|
||||
field="name"
|
||||
expander
|
||||
class="text-break"
|
||||
>
|
||||
<template #header>
|
||||
<div class="text-right">
|
||||
<div class="p-input-icon-left">
|
||||
<i class="pi pi-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
v-model="filters['global']"
|
||||
class="form-control ps-5"
|
||||
placeholder="Search"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #body="{ node }">
|
||||
<treemenu-entry
|
||||
:node="node"
|
||||
:data-tree-item-key="node.key"
|
||||
v-drag-click="() => toggleTreeNode(node)"
|
||||
@drop="$emit('drop', $event)"
|
||||
/>
|
||||
</template>
|
||||
</pv-column>
|
||||
<pv-column
|
||||
field="fav"
|
||||
class="flex-shrink-0 flex-grow-0"
|
||||
header-class="flex-shrink-0 flex-grow-0"
|
||||
>
|
||||
<template #header>
|
||||
<a
|
||||
v-if="favorites.on || favorites.list.length"
|
||||
href="#"
|
||||
@click.prevent="filterFav"
|
||||
>
|
||||
<i
|
||||
:class="favorites.on ? 'fa-solid' : 'fa-regular'"
|
||||
class="fa-star"
|
||||
></i>
|
||||
</a>
|
||||
</template>
|
||||
<template #body="{ node }">
|
||||
<a
|
||||
v-if="node.data.root"
|
||||
href="#"
|
||||
tabindex="-1"
|
||||
data-link-fav-add
|
||||
@click.prevent="markFav(node)"
|
||||
@keydown.enter.stop.prevent="markFav(node)"
|
||||
>
|
||||
<i
|
||||
:class="favorites.list.includes(node.data.path + '') ? 'fa-solid' : 'fa-regular'"
|
||||
class="fa-star"
|
||||
></i>
|
||||
</a>
|
||||
</template>
|
||||
</pv-column>
|
||||
<pv-column field="search" class="d-none"></pv-column>
|
||||
</pv-treetable>`
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import drop from '../../../directives/drop.js';
|
||||
import draggable from '../../../directives/draggable.js';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
drop,
|
||||
draggable
|
||||
},
|
||||
emits: [
|
||||
'drop'
|
||||
],
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
if (Array.isArray(this.node.data.name))
|
||||
return this.$p.t(this.node.data.name);
|
||||
|
||||
return this.node.data.name;
|
||||
},
|
||||
title() {
|
||||
if (!this.node.data.title)
|
||||
return this.name;
|
||||
|
||||
if (Array.isArray(this.node.data.title))
|
||||
return this.$p.t(this.node.data.title);
|
||||
|
||||
return this.node.data.title;
|
||||
},
|
||||
dropConfig() {
|
||||
if (!this.node.data?.droplink)
|
||||
return null;
|
||||
|
||||
const allowed = [ ...this.node.data.droplink ];
|
||||
const effect = allowed.shift();
|
||||
|
||||
return { effect, allowed };
|
||||
},
|
||||
dragConfig() {
|
||||
if (!this.node.data?.draggable)
|
||||
return null;
|
||||
|
||||
let config = [ ...this.node.data.draggable ];
|
||||
const effect = config.shift();
|
||||
const value = JSON.parse(config.shift());
|
||||
|
||||
return { effect, value };
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<span
|
||||
class="treemenu-entry d-flex align-items-center w-100 h-100"
|
||||
:title="title"
|
||||
v-drop:[dropConfig]="(evt, data) => $emit('drop', { drop: node.data, drag: data })"
|
||||
v-draggable:[dragConfig?.effect]="dragConfig?.value"
|
||||
>
|
||||
{{ name }}
|
||||
</span>`
|
||||
};
|
||||
@@ -174,6 +174,9 @@ export default {
|
||||
>
|
||||
{{ $p.t('lehre/noLvFound') }}
|
||||
</div>
|
||||
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
|
||||
<span class="placeholder col-12"></span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
|
||||
|
||||
@@ -79,6 +79,9 @@ export default {
|
||||
>
|
||||
{{ $p.t('lehre/noLvFound') }}
|
||||
</div>
|
||||
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
|
||||
<span class="placeholder col-12"></span>
|
||||
</div>
|
||||
<component
|
||||
v-else-if="mode == 'eventheader'"
|
||||
:is="renderers[event.type]?.modalTitle"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
export default {
|
||||
name: "Compat",
|
||||
props: {
|
||||
mode: { type: String, required: true },
|
||||
path: { type: String, required: true },
|
||||
query_string: { type: String, required: true, default: ''}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
lastLoadediFrameURL: '',
|
||||
srcUrl: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
propsWatchHelper: function() {
|
||||
return this.mode + '#' + this.path + '#' + this.query_string;
|
||||
}
|
||||
},
|
||||
mounted: function() {
|
||||
this.srcUrl = this.buildSrcUrl();
|
||||
},
|
||||
watch: {
|
||||
propsWatchHelper: function() {
|
||||
if(this.lastLoadediFrameURL === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentiFrameURL = this.$refs.compatiframe ? this.$refs.compatiframe.src : '';
|
||||
|
||||
let url = this.buildSrcUrl();
|
||||
if(this.lastLoadediFrameURL !== url) {
|
||||
this.srcUrl = url;
|
||||
if(this.srcUrl === url) {
|
||||
this.$refs.compatiframe.contentWindow.location.href = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildSrcUrl: function() {
|
||||
let url = false;
|
||||
switch(this.mode) {
|
||||
case 'ci':
|
||||
url = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'index.ci.php/' + this.path;
|
||||
break;
|
||||
case 'legacy':
|
||||
url = FHC_JS_DATA_STORAGE_OBJECT.app_root + this.path;
|
||||
break;
|
||||
default:
|
||||
url = false;
|
||||
}
|
||||
if(this.query_string !== '' && url) {
|
||||
url += '?' + this.query_string;
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
loadHandler: function() {
|
||||
let iframe_href = this.$refs.compatiframe.contentWindow.location.href;
|
||||
let ci_urlstart = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'index.ci.php/';
|
||||
let legacy_urlstart = FHC_JS_DATA_STORAGE_OBJECT.app_root;
|
||||
let routerpath = null;
|
||||
|
||||
this.lastLoadediFrameURL = iframe_href;
|
||||
|
||||
if(iframe_href.startsWith(ci_urlstart)) {
|
||||
routerpath = iframe_href.replace(
|
||||
ci_urlstart, '/Cis/Compat/ci/');
|
||||
} else if(iframe_href.startsWith(legacy_urlstart)) {
|
||||
routerpath = iframe_href.replace(
|
||||
legacy_urlstart, '/Cis/Compat/legacy/');
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.$route.fullPath !== routerpath) {
|
||||
this.$router.push(routerpath);
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="w-100">
|
||||
<iframe
|
||||
ref="compatiframe"
|
||||
v-if="srcUrl"
|
||||
:src="srcUrl"
|
||||
@load="loadHandler"
|
||||
style="width:100%; height:90vh; border:0; display:block;"
|
||||
></iframe>
|
||||
<div v-else class="alert alert-warning">Keine URL gefunden.</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
@@ -32,7 +32,7 @@ export default {
|
||||
listGroup: [],
|
||||
rangeIntervalFirst: null,
|
||||
otherPersonData: {
|
||||
fullName: "",
|
||||
fullNameWithTitle: "",
|
||||
photo: "",
|
||||
},
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
|
||||
@@ -210,10 +210,17 @@ export default {
|
||||
this.isOtherPersonMitarbeiter =
|
||||
!!viewData?.user_data?.is_mitarbeiter;
|
||||
this.isOtherPersonStudent = !!viewData?.user_data?.is_student;
|
||||
this.otherPersonData.fullName =
|
||||
viewData?.user_data?.vorname +
|
||||
" " +
|
||||
viewData?.user_data?.nachname;
|
||||
let fullNameWithTitleFragments = [
|
||||
viewData?.user_data?.titel,
|
||||
viewData?.user_data?.vorname,
|
||||
viewData?.user_data?.nachname,
|
||||
viewData?.user_data?.postnomen,
|
||||
];
|
||||
fullNameWithTitleFragments = fullNameWithTitleFragments.filter(
|
||||
(fragment) => !!fragment,
|
||||
);
|
||||
this.otherPersonData.fullNameWithTitle =
|
||||
fullNameWithTitleFragments.join(" ");
|
||||
this.otherPersonData.photo = viewData?.user_data?.foto;
|
||||
},
|
||||
async redirectToMyLvPlanIfAuthUid() {
|
||||
@@ -225,12 +232,21 @@ export default {
|
||||
this.$router.push({ name: "MyLvPlan" });
|
||||
}
|
||||
},
|
||||
copyFullNameWithTitleToClipboard() {
|
||||
navigator.clipboard.writeText(
|
||||
this.otherPersonData.fullNameWithTitle,
|
||||
);
|
||||
this.$fhcAlert.alertSuccess(
|
||||
this.$p.t("profil/name_title_clipboard_copy_confirmation"),
|
||||
);
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.redirectToMyLvPlanIfAuthUid();
|
||||
await this.fetchViewData();
|
||||
await this.$p.loadCategory(["profil"]);
|
||||
},
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<div class="d-flex flex-column h-100">
|
||||
<h2>
|
||||
<div class="d-flex flex-row justify-content-between align-items-center">
|
||||
@@ -238,8 +254,11 @@ export default {
|
||||
{{ $p.t('lehre/stundenplan') + (studiensemester_kurzbz ? " " + studiensemester_kurzbz : "") }}
|
||||
</span>
|
||||
<div @click="this.$router.push({name: 'ProfilView', params: {uid: propsViewData.otherUid}})" type="button" class="d-flex flex-row align-items-center gap-3">
|
||||
<span v-if="otherPersonData.fullName?.length">
|
||||
{{ otherPersonData.fullName }}
|
||||
<span v-if="otherPersonData.fullNameWithTitle?.length">
|
||||
{{ otherPersonData.fullNameWithTitle }}
|
||||
<span @click.stop="copyFullNameWithTitleToClipboard()" :title="$p.t('LvPlan/copy')" class="fs-4 h-100">
|
||||
<i class="fa-regular fa-copy"></i>
|
||||
</span>
|
||||
</span>
|
||||
<img v-if="otherPersonData.photo?.length" alt="profile picture" class=" img-thumbnail " style=" max-height:60px; " :src="get_image_base64_src"/>
|
||||
</div>
|
||||
|
||||
@@ -72,20 +72,10 @@ export default {
|
||||
return this.entry.url.startsWith(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router)
|
||||
},
|
||||
target() {
|
||||
if (this.entry.template_kurzbz == 'redirect') {
|
||||
if (!this.entry.content)
|
||||
return '';
|
||||
let xmlDoc = (new DOMParser()).parseFromString(this.entry.content,"text/xml");
|
||||
let target = xmlDoc.getElementsByTagName('target')[0];
|
||||
if (!target)
|
||||
return '';
|
||||
|
||||
target = target.childNodes[0].nodeValue + "";
|
||||
if (target == 'content' || target == '_self')
|
||||
target = "";
|
||||
return target;
|
||||
if (this.entry?.target) {
|
||||
return this.entry.target;
|
||||
}
|
||||
return ''
|
||||
return '_self';
|
||||
},
|
||||
hasChilds() {
|
||||
return this.entry.childs && this.entry.childs.length !== 0;
|
||||
|
||||
@@ -469,6 +469,7 @@ export default {
|
||||
:tabulator-options="betriebsmittel_table_options"
|
||||
tableOnly
|
||||
:sideMenu="false"
|
||||
:isUsingPresets="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,6 +43,7 @@ export default {
|
||||
maxWidth: 40,
|
||||
headerClick: this.collapseFunction,
|
||||
visible: true,
|
||||
responsive: 0,
|
||||
},
|
||||
{
|
||||
title: "placeholder",
|
||||
@@ -51,6 +52,7 @@ export default {
|
||||
headerFilter: true,
|
||||
minWidth: 200,
|
||||
visible: true,
|
||||
responsive: 0,
|
||||
},
|
||||
{
|
||||
title: "placeholder",
|
||||
@@ -59,6 +61,7 @@ export default {
|
||||
headerFilter: true,
|
||||
minWidth: 200,
|
||||
visible: true,
|
||||
responsive: 1,
|
||||
},
|
||||
{
|
||||
title: "placeholder",
|
||||
@@ -71,6 +74,7 @@ export default {
|
||||
visible: true,
|
||||
formatter: "datetime",
|
||||
formatterParams: this.datetimeFormatterParams(),
|
||||
responsive: 4,
|
||||
},
|
||||
{
|
||||
title: "placeholder",
|
||||
@@ -83,6 +87,7 @@ export default {
|
||||
visible: true,
|
||||
formatter: "datetime",
|
||||
formatterParams: this.datetimeFormatterParams(),
|
||||
responsive: 3,
|
||||
},
|
||||
{
|
||||
title: "placeholder",
|
||||
@@ -91,6 +96,7 @@ export default {
|
||||
headerFilter: true,
|
||||
minWidth: 200,
|
||||
visible: true,
|
||||
responsive: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -176,7 +176,6 @@ export const Profil = {
|
||||
this.data = data.profil_data.data;
|
||||
this.calendarSyncUrls = data.calendar_sync_urls ?? [];
|
||||
this.authPermissions = data.permissions;
|
||||
console.log(data.profil_data);
|
||||
},
|
||||
zustellAdressenCount() {
|
||||
if (!this.data || !this.data.adressen) {
|
||||
|
||||
@@ -2,15 +2,16 @@ export default {
|
||||
props: {
|
||||
data: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {};
|
||||
},
|
||||
template: /*html*/`
|
||||
template: /*html*/ `
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<span >{{$p.t('profil','fhAusweisStatus',[data])}}</span>
|
||||
<span v-if="data">{{$p.t('profil','fhAusweisStatus',[data])}}</span>
|
||||
<span v-else>{{$p.t('profil','fhAusweisStatusNichtAusgegeben')}}</span>
|
||||
</div>
|
||||
</div>`,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,6 +67,21 @@ export default {
|
||||
return res;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
copyFullNameWithTitleToClipboard() {
|
||||
let fragments = [
|
||||
this.$props.data.Titel,
|
||||
this.$props.data.Vorname,
|
||||
this.$props.data.Nachname,
|
||||
this.$props.data.Postnomen,
|
||||
];
|
||||
fragments = fragments.filter((fragment) => !!fragment);
|
||||
navigator.clipboard.writeText(fragments.join(" "));
|
||||
this.$fhcAlert.alertSuccess(
|
||||
this.$p.t("profil/name_title_clipboard_copy_confirmation"),
|
||||
);
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
|
||||
<div class="card h-100">
|
||||
@@ -124,6 +139,16 @@ export default {
|
||||
<span class="form-underline-content">{{wert?wert:'-'}} </span>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
@click="copyFullNameWithTitleToClipboard()"
|
||||
href="#"
|
||||
class="col-12 fhc-link-color d-flex flex-row gap-2 align-items-center justify-content-center mt-3"
|
||||
>
|
||||
<span>
|
||||
<i class="fa-regular fa-copy ms-2 text-decoration-none"></i>
|
||||
</span>
|
||||
<span>{{ $p.t("profil/name_title_copy") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,16 @@ export default {
|
||||
return numberPadding(this.event.ende.getHours()) + ":" + numberPadding(this.event.ende.getMinutes());
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
event: {
|
||||
async handler() {
|
||||
if (this.$props.event.type == "lehreinheit") {
|
||||
await this.getLvMenu();
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
mehtodNumberPadding: function (number) {
|
||||
return numberPadding(number);
|
||||
@@ -58,21 +68,31 @@ export default {
|
||||
methodFormatDate: function (d) {
|
||||
return formatDate(d);
|
||||
},
|
||||
async getLvMenu() {
|
||||
const semesterResponse = await this.$api.call(
|
||||
ApiLvPlan.getLehreinheitStudiensemester(
|
||||
Array.isArray(this.event.lehreinheit_id)
|
||||
? this.event.lehreinheit_id[0]
|
||||
: this.event.lehreinheit_id,
|
||||
),
|
||||
);
|
||||
if (semesterResponse.meta.status !== "success") return;
|
||||
|
||||
const studiensemesterKurzbz = semesterResponse.data;
|
||||
const lvMenuResponse = await this.$api.call(
|
||||
ApiAddons.getLvMenu(
|
||||
this.event.lehrveranstaltung_id,
|
||||
studiensemesterKurzbz,
|
||||
),
|
||||
);
|
||||
if (lvMenuResponse.meta.status !== "success") return;
|
||||
|
||||
this.lvMenu = lvMenuResponse.data;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.event.type == 'lehreinheit') {
|
||||
this.$api
|
||||
.call(ApiLvPlan.getLehreinheitStudiensemester(Array.isArray(this.event.lehreinheit_id) ? this.event.lehreinheit_id[0] : this.event.lehreinheit_id))
|
||||
.then(res => res.data)
|
||||
.then(studiensemester_kurzbz => this.$api.call(
|
||||
ApiAddons.getLvMenu(
|
||||
this.event.lehrveranstaltung_id,
|
||||
studiensemester_kurzbz
|
||||
)
|
||||
))
|
||||
.then(res => {
|
||||
this.lvMenu = res.data;
|
||||
});
|
||||
async created() {
|
||||
if (this.$props.event.type == 'lehreinheit') {
|
||||
await this.getLvMenu();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
|
||||
@@ -206,6 +206,8 @@ export default {
|
||||
});
|
||||
|
||||
this.lehrveranstaltungen.forEach((lehrveranstaltung)=>{
|
||||
// a lehrveranstaltung without a modul has no children
|
||||
if(!Array.isArray(lehrveranstaltung.lehrveranstaltungen)) return;
|
||||
lehrveranstaltung.lehrveranstaltungen.sort((lv1,lv2)=>{
|
||||
if (lv1.bezeichnung.toLowerCase() > lv2.bezeichnung.toLowerCase()) {
|
||||
return 1;
|
||||
@@ -381,18 +383,28 @@ export default {
|
||||
<template v-for="lehrveranstaltung in lehrveranstaltungen" :key="lehrveranstaltung.lehrveranstaltung_id">
|
||||
<div class="card" v-if="Array.isArray(lehrveranstaltung.lehrveranstaltungen) && lehrveranstaltung.lehrveranstaltungen.length >0" >
|
||||
<div class="card-header">
|
||||
<h5 class=" card-title">{{isGermanLanguage == 'German' ? lehrveranstaltung.bezeichnung : lehrveranstaltung.bezeichnung_english }}</h5>
|
||||
<h5 class=" card-title">{{isGermanLanguage ? lehrveranstaltung.bezeichnung : lehrveranstaltung.bezeichnung_english }}</h5>
|
||||
<h6 class=" card-subtitle">{{lehrveranstaltung.lehrform_kurzbz}}</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="d-flex list-group-item" v-for="lv in lehrveranstaltung.lehrveranstaltungen">
|
||||
<a class="fhc-link-color d-block me-auto" href="#" @click="openLvUebersicht(lv)">{{isGermanLanguage == 'German' ? lv.bezeichnung : lv.bezeichnung_english}}</a>
|
||||
<a class="fhc-link-color d-block me-auto" href="#" @click="openLvUebersicht(lv)">{{isGermanLanguage ? lv.bezeichnung : lv.bezeichnung_english}}</a>
|
||||
<p>{{lv.lehrform_kurzbz}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" v-else-if="!Array.isArray(lehrveranstaltung.lehrveranstaltungen)">
|
||||
<div class="card-body">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="d-flex list-group-item">
|
||||
<a class="fhc-link-color d-block me-auto" href="#" @click="openLvUebersicht(lehrveranstaltung)">{{isGermanLanguage ? lehrveranstaltung.bezeichnung : lehrveranstaltung.bezeichnung_english}}</a>
|
||||
<p>{{lehrveranstaltung.lehrform_kurzbz}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ export default {
|
||||
|
||||
return [type, result];
|
||||
}));
|
||||
},
|
||||
hiddenWidgets() {
|
||||
return this.widgets.filter(widget => widget.hidden);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -69,6 +72,9 @@ export default {
|
||||
this.$refs.widgetpicker
|
||||
.getWidget()
|
||||
.then(widget_id => {
|
||||
if (widget_id.hidden)
|
||||
this.widgetUnhide(widget_id, widget.place);
|
||||
|
||||
widget.widget = widget_id;
|
||||
// NOTE(chris): min size
|
||||
widget.place = Object.fromEntries(Object.entries(widget.place).map(([key, value]) => {
|
||||
@@ -131,17 +137,6 @@ export default {
|
||||
this.widgets.forEach((widget, i) => {
|
||||
if (failed.includes(widget.id)) {
|
||||
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
|
||||
/** NOTE(chris): if you wanna hide or unhide a
|
||||
* preset and it fails: switch around the hidden
|
||||
* value to revert it properly (checkboxes can't
|
||||
* really handle it otherwise)
|
||||
*/
|
||||
if (payload[widget.id].hidden !== undefined) {
|
||||
this.widgets[i].hidden = payload[widget.id].hidden;
|
||||
this.$nextTick(() => {
|
||||
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
|
||||
});
|
||||
}
|
||||
} else if (payload[widget.id]) {
|
||||
payload[widget.id].id = widget.id;
|
||||
payload[widget.id].index = widget.index;
|
||||
@@ -160,6 +155,31 @@ export default {
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
widgetUnhide(orig, place) {
|
||||
place = Object.fromEntries(Object.entries(place).map(([key, value]) => {
|
||||
value.w = this.sizeLimits[orig.widget].width.min;
|
||||
value.h = this.sizeLimits[orig.widget].height.min;
|
||||
// NOTE(chris): unpin pinned widgets because they got a new placement
|
||||
value.pinned = false;
|
||||
return [key, value];
|
||||
}));
|
||||
// NOTE(chris): clear placement in other screensizes
|
||||
Object.keys(orig.place).forEach(key => {
|
||||
if (!place[key]) {
|
||||
place[key] = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
pinned: false
|
||||
};
|
||||
}
|
||||
});
|
||||
let payload = {};
|
||||
payload[orig.id] = {
|
||||
hidden: false,
|
||||
place
|
||||
};
|
||||
return this.widgetUpdate(payload);
|
||||
},
|
||||
async fetchViewData() {
|
||||
let viewDataResult = await this.$api.call(ApiDashboard.getViewData());
|
||||
const viewData = viewDataResult.data;
|
||||
@@ -204,15 +224,23 @@ export default {
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="core-dashboard">
|
||||
<h3>
|
||||
{{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }}
|
||||
<button
|
||||
class="btn ms-2"
|
||||
aria-label="edit dashboard"
|
||||
v-tooltip="{ showDelay: 1000, value: $p.t('dashboard/edit') }"
|
||||
@click="editMode = !editMode"
|
||||
><i class="fa-solid fa-gear" aria-hidden="true"></i></button>
|
||||
</h3>
|
||||
<header class="d-flex justify-content-between align-items-baseline">
|
||||
<h3>
|
||||
{{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }}
|
||||
</h3>
|
||||
<div class="form-check form-check-reverse form-switch">
|
||||
<label class="form-check-label" for="switchEditMode">
|
||||
{{ $p.t('dashboard/edit') }}
|
||||
</label>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="switchEditMode"
|
||||
v-model="editMode"
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
<dashboard-section
|
||||
name="general"
|
||||
:widgets="widgets"
|
||||
@@ -223,6 +251,7 @@ export default {
|
||||
<dashboard-widget-picker
|
||||
ref="widgetpicker"
|
||||
:widgets="widgetsSetup"
|
||||
:hidden-widgets="hiddenWidgets"
|
||||
></dashboard-widget-picker>
|
||||
</div>`
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ export default {
|
||||
"width",
|
||||
"height",
|
||||
"custom",
|
||||
"hidden",
|
||||
"editMode",
|
||||
"loading", // widget got added and is waiting for backend to save in db
|
||||
"item_data",
|
||||
@@ -122,14 +121,6 @@ export default {
|
||||
},
|
||||
ready() {
|
||||
return this.component && this.arguments !== null;
|
||||
},
|
||||
visible: {
|
||||
get() {
|
||||
return !this.hidden;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('remove', this.hidden);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -213,10 +204,8 @@ export default {
|
||||
},
|
||||
template: /*html*/ `
|
||||
<article
|
||||
v-if="!hidden || editMode"
|
||||
class="dashboard-item card overflow-hidden h-100 position-relative"
|
||||
:class="{
|
||||
'hidden-widget': hidden,
|
||||
[arguments?.className]: arguments && arguments.className
|
||||
}"
|
||||
>
|
||||
@@ -310,9 +299,9 @@ export default {
|
||||
>
|
||||
<i class="fa-solid fa-gear" aria-hidden="true"></i>
|
||||
</a>
|
||||
<!-- delete button -->
|
||||
<!-- delete/hide button -->
|
||||
<a
|
||||
v-if="custom && editMode"
|
||||
v-if="editMode"
|
||||
href="#"
|
||||
class="col-auto px-1"
|
||||
:aria-label="$p.t('dashboard/widget_delete')"
|
||||
@@ -321,19 +310,6 @@ export default {
|
||||
>
|
||||
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||||
</a>
|
||||
<!-- hide button -->
|
||||
<Transition>
|
||||
<div v-if="!custom && editMode" class="col-auto px-1 form-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
v-model="visible"
|
||||
class="form-check-input ms-0"
|
||||
:value="true"
|
||||
:aria-label="$p.t('dashboard/widget_toggle_visibility')"
|
||||
>
|
||||
</div>
|
||||
</Transition>
|
||||
</header>
|
||||
<!-- TODO Manu rename/remove-->
|
||||
<div v-if="ready" class="card-body overflow-hidden p-0">
|
||||
|
||||
@@ -124,8 +124,6 @@ export default {
|
||||
return { ...item, ...placement, weight };
|
||||
});
|
||||
|
||||
if (this.editModeIsActive)
|
||||
return placedItems;
|
||||
return placedItems.filter(item => !item.hidden);
|
||||
}
|
||||
},
|
||||
@@ -141,22 +139,12 @@ export default {
|
||||
handleConfigClosed() {
|
||||
this.configOpened = false
|
||||
},
|
||||
removeWidget(item, revert) {
|
||||
removeWidget(item) {
|
||||
if (item.custom) {
|
||||
BsConfirm.popup(this.$p.t('dashboard', 'alert_deleteWidget')).then(() => this.$emit('widgetRemove', item.id, this.name));
|
||||
} else {
|
||||
let update = {};
|
||||
update[item.id] = { hidden: !revert };
|
||||
|
||||
if (!revert) {
|
||||
// NOTE(chris): move to last line
|
||||
update[item.id].place = [];
|
||||
let y = this.gridHeight;
|
||||
if (this.additionalRow)
|
||||
y--;
|
||||
update[item.id].place[this.gridWidth] = { x: 0, y };
|
||||
}
|
||||
|
||||
update[item.id] = { hidden: true };
|
||||
this.updatePreset(update);
|
||||
}
|
||||
},
|
||||
@@ -251,13 +239,12 @@ export default {
|
||||
:loading="item.loading"
|
||||
:config="item.config"
|
||||
:custom="item.custom"
|
||||
:hidden="item.hidden"
|
||||
:editMode="editModeIsActive"
|
||||
:place="item.place[gridWidth]"
|
||||
:widget-template="indexedWidgetsTemplates[item.widget]"
|
||||
:source="adminMode ? null : item.source || 'custom'"
|
||||
@change="saveConfig($event, item)"
|
||||
@remove="removeWidget(item, $event)"
|
||||
@remove="removeWidget(item)"
|
||||
@config-opened="handleConfigOpened"
|
||||
@config-closed="handleConfigClosed"
|
||||
@pin-item="updatePositions"
|
||||
|
||||
@@ -7,7 +7,8 @@ export default {
|
||||
WidgetIcon,
|
||||
},
|
||||
props: [
|
||||
"widgets"
|
||||
"widgets",
|
||||
"hiddenWidgets"
|
||||
],
|
||||
data: () => ({
|
||||
callbacks: {}
|
||||
@@ -16,6 +17,13 @@ export default {
|
||||
// filter widgets away the user has no permissions for
|
||||
availableWidgets() {
|
||||
return (this.widgets || []).filter(widget => widget.permitted !== false);
|
||||
},
|
||||
hasAnyWidgets() {
|
||||
if (!this.widgets)
|
||||
return false;
|
||||
if (!this.availableWidgets.length && !this.hiddenWidgets?.length)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -43,23 +51,47 @@ export default {
|
||||
<bs-modal
|
||||
ref="modal"
|
||||
class="fade"
|
||||
:dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': availableWidgets.length > 0 }"
|
||||
:dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': hasAnyWidgets }"
|
||||
@hiddenBsModal="close"
|
||||
>
|
||||
<template v-slot:title>{{ $p.t('dashboard/createWidget') }}</template>
|
||||
<template v-slot:default>
|
||||
<div v-if="widgets" class="row g-2">
|
||||
<div v-if="!availableWidgets.length">
|
||||
{{ $p.t('dashboard/noWidgetsAvailable') }}
|
||||
</div>
|
||||
<template v-if="widgets && hiddenWidgets">
|
||||
<div
|
||||
v-for="widget in availableWidgets"
|
||||
:key="widget.widget_id"
|
||||
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
|
||||
v-if="!availableWidgets.length && !hiddenWidgets.length"
|
||||
class="row g-2"
|
||||
>
|
||||
<widget-icon @select="pick" :widget="widget"></widget-icon>
|
||||
<div>{{ $p.t('dashboard/noWidgetsAvailable') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="hiddenWidgets.length"
|
||||
class="row g-2"
|
||||
:class="widgets.length ? 'border-bottom pb-3 mb-3' : ''"
|
||||
>
|
||||
<div
|
||||
v-for="widget in hiddenWidgets"
|
||||
:key="widget.widget_id"
|
||||
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
|
||||
>
|
||||
<widget-icon @select="pick(widget)" :widget="widgets.find(w => w.widget_id == widget.widget)"></widget-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="availableWidgets.length"
|
||||
class="row g-2"
|
||||
>
|
||||
<template
|
||||
v-for="widget in availableWidgets"
|
||||
:key="widget.widget_id"
|
||||
>
|
||||
<div v-if="widget.permitted" class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2">
|
||||
<widget-icon @select="pick" :widget="widget"></widget-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<div v-else class="text-center">
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import ApiDetailHeader from "../../api/factory/detailHeader.js";
|
||||
import ApiHandleFoto from "../../api/factory/fotoHandling.js";
|
||||
import ModalUploadFoto from "./Modal/UploadFoto.js";
|
||||
import PvSkeleton from "../../../../index.ci.php/public/js/components/primevue/skeleton/skeleton.esm.min.js";
|
||||
import CoreTag from "../Tag/Tag.js";
|
||||
import ApiTag from "../../api/factory/stv/tag.js";
|
||||
import { idTagFormatter } from "../Tag/tagFormatter.js";
|
||||
|
||||
export default {
|
||||
name: 'DetailHeader',
|
||||
components: {
|
||||
ModalUploadFoto
|
||||
ModalUploadFoto,
|
||||
PvSkeleton,
|
||||
CoreTag
|
||||
},
|
||||
props: {
|
||||
headerData: {
|
||||
@@ -38,8 +44,26 @@ export default {
|
||||
'mitarbeiter',
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
currentSemester: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isLoading: { //if true, then parent isLoading
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
tagsEnabled: {
|
||||
from: 'configStvTagsEnabled',
|
||||
default: false
|
||||
},
|
||||
lists: {
|
||||
from: 'lists',
|
||||
required: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
appRoot() {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root;
|
||||
@@ -58,20 +82,36 @@ export default {
|
||||
hasTileGammaSlot() {
|
||||
return !!this.$slots.titleGammaTile
|
||||
},
|
||||
hasTileDeltaSlot() {
|
||||
return !!this.$slots.titleDeltaTile
|
||||
},
|
||||
hasTileUIDSlot() {
|
||||
return !!this.$slots.uid
|
||||
},
|
||||
|
||||
prestudentIds() {
|
||||
if (this.headerData[0].prestudent_id)
|
||||
{
|
||||
return [this.headerData[0].prestudent_id];
|
||||
}
|
||||
},
|
||||
semesterDates(){
|
||||
return this.lists.studiensemester?.find(item => item.studiensemester_kurzbz === this.currentSemester) || {};
|
||||
}
|
||||
},
|
||||
created(){
|
||||
if (this.typeHeader === 'student') {
|
||||
if (!this.headerData) {
|
||||
throw new Error('[DetailHeader] "headerData" is required.')
|
||||
}
|
||||
|
||||
if(this.tagsEnabled) {
|
||||
this.loadTagsAndRender(this.headerData[0].prestudent_id);
|
||||
}
|
||||
|
||||
} else if (this.typeHeader === 'mitarbeiter') {
|
||||
if (!this.person_id || !this.mitarbeiter_uid || !this.domain) {
|
||||
throw new Error(
|
||||
'[DetailHeader] "person_id", "mitarbeiter_uid", and "domain" are requried.'
|
||||
'[DetailHeader] "person_id", "mitarbeiter_uid", and "domain" are required.'
|
||||
)
|
||||
}
|
||||
this.loadHeaderData(this.person_id, this.mitarbeiter_uid);
|
||||
@@ -86,13 +126,34 @@ export default {
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
headerData: {
|
||||
handler(newVal) {
|
||||
if (this.typeHeader === 'student' && newVal?.length) {
|
||||
this.getSemesterStati(newVal[0].prestudent_id);
|
||||
}
|
||||
|
||||
if(this.tagsEnabled) {
|
||||
this.loadTagsAndRender(this.headerData[0].prestudent_id);
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
headerDataMa: {},
|
||||
departmentData: {},
|
||||
leitungData: {},
|
||||
isFetchingIssues: false
|
||||
isFetchingIssues: false,
|
||||
noCurrentStatus: false,
|
||||
semesterStatiLoading: false,
|
||||
leitungOrgLoading: false,
|
||||
departmentDataLoading: false,
|
||||
headerDataMaLoading: false,
|
||||
tagEndpoint: ApiTag,
|
||||
tagData: null,
|
||||
rebuildData: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -108,29 +169,40 @@ export default {
|
||||
});
|
||||
},
|
||||
getHeader(person_id) {
|
||||
this.headerDataMaLoading = true;
|
||||
return this.$api
|
||||
.call(ApiDetailHeader.getHeader(person_id))
|
||||
.then(result => {
|
||||
this.headerDataMa = result.data;
|
||||
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.headerDataMaLoading = false;
|
||||
});
|
||||
},
|
||||
loadDepartmentData(mitarbeiter_uid) {
|
||||
this.departmentDataLoading = true;
|
||||
return this.$api
|
||||
.call(ApiDetailHeader.getPersonAbteilung(mitarbeiter_uid))
|
||||
.then(result => {
|
||||
this.departmentData = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.departmentDataLoading = false;
|
||||
});
|
||||
},
|
||||
getLeitungOrg(oekurzbz){
|
||||
this.leitungOrgLoading = true;
|
||||
return this.$api
|
||||
.call(ApiDetailHeader.getLeitungOrg(oekurzbz))
|
||||
.then(result => {
|
||||
this.leitungData = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.leitungOrgLoading = false;
|
||||
});
|
||||
},
|
||||
async goToLeitung() {
|
||||
this.loadHeaderData(this.leitungData.person_id, this.leitungData.uid);
|
||||
@@ -179,7 +251,98 @@ export default {
|
||||
} else {
|
||||
return 'data:image/jpeg;base64,' + foto;
|
||||
}
|
||||
}
|
||||
},
|
||||
getSemesterStati(prestudent_id){
|
||||
this.semesterStatiLoading = true;
|
||||
this.$api
|
||||
.call(ApiDetailHeader.getSemesterStati(prestudent_id))
|
||||
.then(result => {
|
||||
this.semesterStati = result.data;
|
||||
this.setNoCurrentStatus();
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.semesterStatiLoading = false;
|
||||
});
|
||||
},
|
||||
setNoCurrentStatus() {
|
||||
if(!Array.isArray(this.semesterStati))
|
||||
{
|
||||
this.noCurrentStatus = false;
|
||||
}
|
||||
|
||||
if(!this.semesterStati.some(item => item.studiensemester_kurzbz === this.currentSemester)) {
|
||||
this.noCurrentStatus = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.noCurrentStatus = false;
|
||||
}
|
||||
},
|
||||
//methods tags
|
||||
async loadTagsAndRender(prestudent_id) {
|
||||
await this.getAllTags(prestudent_id);
|
||||
|
||||
const container = idTagFormatter(
|
||||
prestudent_id,
|
||||
this.tagData,
|
||||
this.$refs.tagComponent,
|
||||
'prestudent_id',
|
||||
this.semesterDates.start,
|
||||
this.semesterDates.ende
|
||||
);
|
||||
|
||||
if(this.headerData.length == 1) {
|
||||
this.$refs.tagWrapper.innerHTML = '';
|
||||
this.$refs.tagWrapper.appendChild(container);
|
||||
}
|
||||
},
|
||||
getAllTags(prestudent_id){
|
||||
return this.$api
|
||||
.call(ApiTag.getAllTagsPrestudent({prestudent_id}))
|
||||
.then(result => {
|
||||
this.tagData = result.data.sort((a, b) => {
|
||||
let adone = a.done ? 1 : 0;
|
||||
let bdone = b.done ? 1 : 0;
|
||||
|
||||
if (adone !== bdone) {
|
||||
return adone - bdone;
|
||||
}
|
||||
return a.prioritaet - b.prioritaet;
|
||||
});
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
addedTag(addedTag)
|
||||
{
|
||||
this.reload();
|
||||
},
|
||||
deletedTag(id)
|
||||
{
|
||||
this.reload();
|
||||
},
|
||||
updatedTag(updatedTag)
|
||||
{
|
||||
this.reload();
|
||||
},
|
||||
rebuildPrestudentTags(){
|
||||
const id = [this.headerData[0].prestudent_id];
|
||||
|
||||
const params = {
|
||||
ids: id,
|
||||
typeId: 'prestudent_id',
|
||||
sem: this.currentSemester
|
||||
};
|
||||
|
||||
return this.$api
|
||||
.call(ApiTag.rebuildTagsforTypeId(params))
|
||||
.then(result => {
|
||||
this.rebuildData = result.data;
|
||||
console.log("Rebuild manually triggered");
|
||||
this.reload();
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="core-header d-flex justify-content-start align-items-center w-100 overflow-auto pb-2 gap-3" style="max-height:9rem; min-width: 37.5rem;">
|
||||
@@ -200,7 +363,6 @@ export default {
|
||||
</modal-upload-foto>
|
||||
|
||||
<template v-if="typeHeader==='student'">
|
||||
|
||||
<div
|
||||
v-for="person in headerData"
|
||||
:key="person.person_id"
|
||||
@@ -236,71 +398,154 @@ export default {
|
||||
<small class="text-muted">{{person.uid}}</small>
|
||||
</div>
|
||||
|
||||
<div v-if="headerData.length == 1">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<h2 class="h4">
|
||||
{{headerData[0].titelpre}}
|
||||
{{headerData[0].vorname}}
|
||||
{{headerData[0].nachname}}
|
||||
<span v-if="headerData[0].titelpost">, </span>
|
||||
{{headerData[0].titelpost}}
|
||||
</h2>
|
||||
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
|
||||
<div v-if="headerData.length == 1">
|
||||
<div v-if="!isLoading" class="d-flex align-items-center gap-3">
|
||||
<h2 class="h4 mt-2">
|
||||
{{headerData[0].titelpre}}
|
||||
{{headerData[0].vorname}}
|
||||
{{headerData[0].nachname}}
|
||||
<span v-if="headerData[0].titelpost">, </span>
|
||||
{{headerData[0].titelpost}}
|
||||
</h2>
|
||||
|
||||
<div class="d-flex border rounded align-items-center ps-1">
|
||||
<core-tag ref="tagComponent"
|
||||
v-if="tagsEnabled"
|
||||
:endpoint="tagEndpoint"
|
||||
:values="prestudentIds"
|
||||
:show-hover="true"
|
||||
@added="addedTag"
|
||||
@deleted="deletedTag"
|
||||
@updated="updatedTag"
|
||||
zuordnung_typ="prestudent_id"
|
||||
></core-tag>
|
||||
<div
|
||||
role="button"
|
||||
v-if="tagsEnabled"
|
||||
@click="rebuildPrestudentTags"
|
||||
class="btn btn-outline btn-sm btn-hover m-1"
|
||||
:title="$p.t('tag','rebuild_tags') + ' ' + currentSemester"
|
||||
>
|
||||
<i class="fa-solid fa-refresh pe-1"></i>
|
||||
<span>{{currentSemester}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="h6">
|
||||
<strong class="text-muted">{{$p.t('lehre', 'studiengang')}} </strong>
|
||||
{{headerData[0].stg_bezeichnung}} ({{headerData[0].studiengang}})
|
||||
<strong v-if="headerData[0].semester" class="text-muted"> | {{$p.t('lehre', 'semester')}} </strong>
|
||||
{{headerData[0].semester}}
|
||||
<strong v-if="headerData[0].verband" class="text-muted"> | {{$p.t('lehre', 'verband')}}</strong>
|
||||
{{headerData[0].verband}}
|
||||
<strong v-if="headerData[0].gruppe" class="text-muted"> | {{$p.t('lehre', 'gruppe')}} </strong>
|
||||
{{headerData[0].gruppe}}
|
||||
</h5>
|
||||
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
|
||||
</div>
|
||||
<div v-else class="d-flex align-items-center gap-3">
|
||||
<pv-skeleton width="15rem" height="2rem" borderRadius="16px"></pv-skeleton>
|
||||
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
|
||||
</div>
|
||||
|
||||
<h5 class="h6">
|
||||
<strong class="text-muted">Email </strong>
|
||||
<span>
|
||||
<a :href="'mailto:'+headerData[0]?.mail_intern">{{headerData[0].mail_intern}}</a>
|
||||
</span>
|
||||
<strong v-if="headerData[0].statusofsemester" class="text-muted"> | Status </strong>
|
||||
{{headerData[0].statusofsemester}}
|
||||
</h5>
|
||||
<h5 class="h6 d-flex align-items-center flex-wrap gap-1">
|
||||
<strong class="text-muted">{{$p.t('lehre', 'studiengang')}} </strong>
|
||||
<span v-if="!isLoading">
|
||||
{{headerData[0].stg_bezeichnung}} ({{headerData[0].studiengang}})
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="10rem"></pv-skeleton>
|
||||
</span>
|
||||
<template v-if="!semesterStatiLoading">
|
||||
<strong v-if="headerData[0].semester != null" class="text-muted"> | {{$p.t('lehre', 'semester')}} </strong>
|
||||
{{headerData[0].semester}}
|
||||
<strong v-if="headerData[0].gruppe !== null && headerData[0].verband != ' '" class="text-muted"> | {{$p.t('lehre', 'verband')}}</strong>
|
||||
{{headerData[0].verband}}
|
||||
<strong v-if="headerData[0].gruppe !== null && headerData[0].gruppe != ' '" class="text-muted"> | {{$p.t('lehre', 'gruppe')}} </strong>
|
||||
{{headerData[0].gruppe}}
|
||||
<strong v-if="headerData[0].status=='Interessent'
|
||||
|| headerData[0].status=='Aufgenommener'
|
||||
|| headerData[0].status=='Bewerber'
|
||||
|| headerData[0].status=='Wartender'"
|
||||
class="text-muted"> | Einstiegssemester {{headerData[0].semester_berechnet}}
|
||||
</strong>
|
||||
</template>
|
||||
<template v-else>
|
||||
<strong class="text-muted"> | {{$p.t('lehre', 'semester')}} </strong>
|
||||
<pv-skeleton size="1rem" class="mr-2"></pv-skeleton>
|
||||
<strong class="text-muted"> | {{$p.t('lehre', 'verband')}}</strong>
|
||||
<pv-skeleton size="1rem" class="mr-2"></pv-skeleton>
|
||||
<strong class="text-muted"> | {{$p.t('lehre', 'gruppe')}} </strong>
|
||||
<pv-skeleton size="1rem" class="mr-2"></pv-skeleton>
|
||||
</template>
|
||||
</h5>
|
||||
|
||||
</div>
|
||||
<div v-if="headerData.length == 1" class="col-md-1 d-flex flex-column align-items-end justify-content-start ms-auto">
|
||||
<div class="d-flex py-1">
|
||||
<div class="px-2" style="min-width: 100px;">
|
||||
<slot name="issues"></slot>
|
||||
</div>
|
||||
<div v-if="hasTileGammaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleGammaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueGammaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileBetaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleBetaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueBetaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileAlphaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleAlphaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueAlphaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileUIDSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center">UID</h4>
|
||||
<h6 class="text-muted text-center"><slot name="uid"></slot></h6>
|
||||
</div>
|
||||
<h5 class="h6 d-flex align-items-center flex-wrap gap-1">
|
||||
<strong class="text-muted">Email </strong>
|
||||
<span v-if="!isLoading">
|
||||
<a :href="'mailto:'+headerData[0]?.mail_intern">{{headerData[0].mail_intern}}</a>
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="10rem"></pv-skeleton>
|
||||
</span>
|
||||
<strong class="text-muted"> | {{$p.t('ui', 'private')}} </strong>
|
||||
<span v-if="!isLoading">
|
||||
<a :href="'mailto:'+headerData[0]?.mail_privat">{{headerData[0].mail_privat}}</a>
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="10rem"></pv-skeleton>
|
||||
</span>
|
||||
<strong class="text-muted"> | Status </strong>
|
||||
<span v-if="noCurrentStatus">
|
||||
<strong class="text-danger">{{$p.t('lehre', 'textNoStatusInSem', { sem: currentSemester}) }}</strong>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{headerData[0].statusofsemester}}
|
||||
</span>
|
||||
</h5>
|
||||
<div ref="tagWrapper"></div>
|
||||
</div>
|
||||
<div v-if="headerData.length == 1" class="col-md-1 d-flex flex-column align-items-end justify-content-start ms-auto">
|
||||
<div class="d-flex py-1">
|
||||
<div class="px-2" style="min-width: 100px;">
|
||||
<slot name="issues"></slot>
|
||||
</div>
|
||||
<div v-if="hasTileDeltaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleDeltaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueDeltaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileGammaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleGammaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueGammaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileBetaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleBetaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueBetaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileAlphaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleAlphaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueAlphaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileUIDSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center">UID</h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="uid"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-if="typeHeader==='mitarbeiter'">
|
||||
|
||||
<div class="foto-container col-md-2 d-flex justify-content-start align-items-center w-30 pb-3 gap-3 mt-3 position-relative" style="max-height: 8rem; max-width: 6rem; overflow: hidden;">
|
||||
<div class="foto-container d-flex flex-column align-items-center h-100 position-relative d-inline-block">
|
||||
<img
|
||||
class="d-block w-100 h-100 rounded"
|
||||
style="height: 84px; object-fit: contain;"
|
||||
class="d-block rounded"
|
||||
style="height: 96px;"
|
||||
alt="Profilbild"
|
||||
:src="getFotoSrc(headerDataMa.foto)"
|
||||
/>
|
||||
@@ -313,15 +558,13 @@ export default {
|
||||
<template v-if="fotoEditable">
|
||||
<button
|
||||
type="button"
|
||||
class="fotoedit btn btn-outline-dark btn-sm d-flex justify-content-center align-items-center position-absolute start-0"
|
||||
style="z-index: 104; font-size: 1rem; width: 2.5rem; height: 2.5rem; opacity:0; transition: opacity 0.2s; top:13%;"
|
||||
class="fotoedit buttonleft btn btn-outline-dark btn-sm d-flex justify-content-center align-items-center position-absolute start-0"
|
||||
@click="showDeleteModal(person_id)">
|
||||
<i class="fa fa-xmark"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="fotoedit btn btn-outline-dark btn-sm d-flex justify-content-center align-items-center position-absolute end-0"
|
||||
style="z-index: 104; font-size: 1rem; width: 2.5rem; height: 2.5rem; opacity:0; transition: opacity 0.2s; top:13%;"
|
||||
class="fotoedit buttonright btn btn-outline-dark btn-sm d-flex justify-content-center align-items-center position-absolute end-0"
|
||||
@click="showModal(person_id)">
|
||||
<i class="fa fa-pen"></i>
|
||||
</button>
|
||||
@@ -330,27 +573,51 @@ export default {
|
||||
|
||||
<!--show Ma-Details-->
|
||||
<div class="col-md-9 text-nowrap mt-2">
|
||||
<h4>{{headerDataMa.titelpre}} {{headerDataMa.vorname}} {{headerDataMa.nachname}}<span v-if="headerDataMa?.titelpost">, </span> {{headerDataMa.titelpost}}</h4>
|
||||
<strong class="text-muted">{{departmentData.organisationseinheittyp_kurzbz}}</strong>
|
||||
{{departmentData.bezeichnung}}
|
||||
<span v-if="leitungData.uid"> | </span>
|
||||
<strong v-if="leitungData.uid" class="text-muted">Vorgesetzte*r </strong>
|
||||
<a href="#" @click.prevent="goToLeitung">
|
||||
{{leitungData.titelpre}} {{leitungData.vorname}} {{leitungData.nachname}}
|
||||
</a>
|
||||
<p>
|
||||
<strong class="text-muted">Email </strong>
|
||||
<span v-if="headerDataMa && (headerDataMa.alias === undefined || headerDataMa.alias === null || headerDataMa.alias === '')">
|
||||
<a :href="'mailto:' + mitarbeiter_uid + '@' + domain">
|
||||
{{ mitarbeiter_uid }}@{{ domain }}
|
||||
<h4 v-if="!headerDataMaLoading">{{headerDataMa.titelpre}} {{headerDataMa.vorname}} {{headerDataMa.nachname}}<span v-if="headerDataMa?.titelpost">, </span> {{headerDataMa.titelpost}}</h4>
|
||||
<h4 v-else><pv-skeleton width="15rem" height="2rem" borderRadius="16px"></pv-skeleton></h4>
|
||||
<div class="d-flex align-items-center flex-wrap gap-1">
|
||||
<strong class="text-muted">{{departmentData.organisationseinheittyp_kurzbz}}</strong>
|
||||
<span v-if="!departmentDataLoading">
|
||||
{{departmentData.bezeichnung}}
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="12rem"></pv-skeleton>
|
||||
</span>
|
||||
<span v-if="leitungData.uid"> | </span>
|
||||
<strong v-if="leitungData.uid" class="text-muted">Vorgesetzte*r </strong>
|
||||
<span v-if="!leitungOrgLoading">
|
||||
<a href="#" @click.prevent="goToLeitung">
|
||||
{{leitungData.titelpre}} {{leitungData.vorname}} {{leitungData.nachname}}
|
||||
</a>
|
||||
</span>
|
||||
<span v-else>
|
||||
<a :href="'mailto:'+headerDataMa?.alias+'@'+domain">{{headerDataMa.alias}}@{{domain}}</a>
|
||||
<pv-skeleton width="5rem"></pv-skeleton>
|
||||
</span>
|
||||
<span v-if="headerDataMa?.telefonklappe" class="mb-2"> | <strong class="text-muted">DW </strong>{{headerDataMa?.telefonklappe}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 flex-nowrap">
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<strong class="text-muted">Email</strong>
|
||||
<template v-if="!headerDataMaLoading">
|
||||
<a :href="'mailto:' + (headerDataMa?.alias || mitarbeiter_uid) + '@' + domain">
|
||||
{{ (headerDataMa?.alias || mitarbeiter_uid) + '@' + domain }}
|
||||
</a>
|
||||
</template>
|
||||
<pv-skeleton v-else width="10rem"></pv-skeleton>
|
||||
</div>
|
||||
|
||||
<div v-if="headerDataMa?.telefonklappe" class="d-flex align-items-center gap-1">
|
||||
<span>|</span>
|
||||
<strong class="text-muted">DW</strong>
|
||||
<template v-if="!headerDataMaLoading">
|
||||
{{ headerDataMa.telefonklappe }}
|
||||
</template>
|
||||
<pv-skeleton v-else width="4rem"></pv-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot name="tag"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 d-flex flex-column align-items-end justify-content-start ms-auto">
|
||||
@@ -358,22 +625,42 @@ export default {
|
||||
<div class="px-2" style="min-width: 100px;">
|
||||
<slot name="issues"></slot>
|
||||
</div>
|
||||
<div v-if="hasTileDeltaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleDeltaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueDeltaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileGammaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleGammaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueGammaTile"></slot></h6>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueGammaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileBetaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleBetaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueBetaTile" :valueBetaTile="valueBetaTile"></slot></h6>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueBetaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileAlphaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleAlphaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueAlphaTile"></slot></h6>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueAlphaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileUIDSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center">UID</h4>
|
||||
<h6 class="text-muted text-center"><slot name="uid"></slot></h6>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="uid"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -233,6 +233,14 @@ export default {
|
||||
if (value == oldValue)
|
||||
return;
|
||||
|
||||
/** NOTE(chris): tempPositionUpdates depends on the cols not to be
|
||||
* updated. So if they do we reset to get a clean value. In case the
|
||||
* drag and drop operation is still motion on the next tick this
|
||||
* will be correct again and if there is no such operation we clean
|
||||
* up.
|
||||
*/
|
||||
this.tempPositionUpdates = null;
|
||||
|
||||
this.reinitGrid();
|
||||
},
|
||||
rows: {
|
||||
@@ -246,6 +254,11 @@ export default {
|
||||
},
|
||||
indexedItems: {
|
||||
handler(value) {
|
||||
/** NOTE(chris): there was an update of the items, so in case
|
||||
* it came from a drag and drop operation we need to clean up
|
||||
*/
|
||||
this.tempPositionUpdates = null;
|
||||
|
||||
this.reinitGrid();
|
||||
},
|
||||
immediate: true,
|
||||
|
||||
@@ -29,7 +29,8 @@ export default {
|
||||
label: String,
|
||||
// NOTE(chris): remove these from $attrs array to prevent doubled event listeners
|
||||
onInput: [Array, Function],
|
||||
'onUpdate:modelValue': [Array, Function]
|
||||
'onUpdate:modelValue': [Array, Function],
|
||||
titleActionButton: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -317,6 +318,7 @@ export default {
|
||||
:id="idCmp"
|
||||
:name="name"
|
||||
:class="validationClass"
|
||||
:titleActionButton="titleActionButton"
|
||||
@update:model-value="clearValidationForThisName"
|
||||
>
|
||||
<slot></slot>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
export default {
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
'update:modelValue',
|
||||
'actionbutton-clicked'
|
||||
],
|
||||
props: {
|
||||
modelValue: String
|
||||
modelValue: String,
|
||||
titleActionButton: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valueAsBase64DataString() {
|
||||
@@ -28,6 +33,9 @@ export default {
|
||||
},
|
||||
deleteImage() {
|
||||
this.$emit('update:modelValue', '');
|
||||
},
|
||||
emitAction(){
|
||||
this.$emit('actionbutton-clicked', this.modelValue);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
@@ -42,6 +50,7 @@ export default {
|
||||
<button type="button" class="btn btn-outline-dark btn-sm" @click="openUploadDialog">
|
||||
<i class="fa fa-pen"></i>
|
||||
</button>
|
||||
<button v-if="titleActionButton" class="btn btn-outline-dark btn-sm" @click="emitAction">{{titleActionButton}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,6 +2,7 @@ import {CoreFilterCmpt} from "../../filter/Filter.js";
|
||||
import FormForm from '../../Form/Form.js';
|
||||
import FormInput from '../../Form/Input.js';
|
||||
import ApiGruppe from "../../../api/lehrveranstaltung/gruppe.js";
|
||||
import drop from '../../../directives/drop.js';
|
||||
export default{
|
||||
name: "LVGruppen",
|
||||
components: {
|
||||
@@ -9,6 +10,9 @@ export default{
|
||||
FormForm,
|
||||
FormInput
|
||||
},
|
||||
directives: {
|
||||
drop
|
||||
},
|
||||
props: {
|
||||
lehreinheit_id: Number
|
||||
},
|
||||
@@ -187,28 +191,48 @@ export default{
|
||||
this.reload();
|
||||
})
|
||||
},
|
||||
onDropVerband(node, gruppe) {
|
||||
this.$api.call(ApiGruppe.getGruppe(gruppe))
|
||||
.then(result => result.data)
|
||||
.then(result => {
|
||||
|
||||
let newData = {
|
||||
'gid': result,
|
||||
'lehreinheit_id': this.lehreinheit_id,
|
||||
'lehrverband': gruppe.lehrverband,
|
||||
}
|
||||
this.$api.call(ApiGruppe.add(newData))
|
||||
.then(result => {
|
||||
this.reload()
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
template: `
|
||||
<core-filter-cmpt
|
||||
ref="table"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
:tabulator-events="tabulatorEvents"
|
||||
table-only
|
||||
:side-menu="false"
|
||||
:reload=true
|
||||
>
|
||||
<template #search> <!--TODO (david) Slot prüfen -->
|
||||
<form-input
|
||||
type="autocomplete"
|
||||
:suggestions="filteredGroups"
|
||||
:placeholder="$p.t('lehre', 'addGroup')"
|
||||
v-model="selectedGroup"
|
||||
field="label"
|
||||
@item-select="addGroup"
|
||||
@complete="searchGroup"
|
||||
></form-input>
|
||||
</template>
|
||||
|
||||
</core-filter-cmpt>
|
||||
<div v-drop:link-strict.verband="(evt, gruppe) => onDropVerband(node, gruppe)">
|
||||
<core-filter-cmpt
|
||||
ref="table"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
:tabulator-events="tabulatorEvents"
|
||||
table-only
|
||||
:side-menu="false"
|
||||
:reload=true
|
||||
>
|
||||
<template #search> <!--TODO (david) Slot prüfen -->
|
||||
<form-input
|
||||
type="autocomplete"
|
||||
:suggestions="filteredGroups"
|
||||
:placeholder="$p.t('lehre', 'addGroup')"
|
||||
v-model="selectedGroup"
|
||||
field="label"
|
||||
@item-select="addGroup"
|
||||
@complete="searchGroup"
|
||||
></form-input>
|
||||
</template>
|
||||
|
||||
</core-filter-cmpt>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
@@ -1,16 +1,18 @@
|
||||
import CoreSearchbar from "../searchbar/searchbar.js";
|
||||
import VerticalSplit from "../verticalsplit/verticalsplit.js";
|
||||
import HorizontalSplit from "../horizontalsplit/horizontalsplit.js";
|
||||
import AppMenu from "../AppMenu.js";
|
||||
import StvVerband from "../Stv/Studentenverwaltung/Verband.js";
|
||||
import BaseTreemenu from '../Base/Treemenu.js';
|
||||
import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
|
||||
import LvTable from "./Setup/Table.js";
|
||||
import LvTabs from "./Setup/Tabs.js";
|
||||
import NavLanguage from "../navigation/Language.js";
|
||||
|
||||
import ApiDetails from "../../api/lehrveranstaltung/details.js";
|
||||
import ApiLektor from "../../api/lehrveranstaltung/lektor.js";
|
||||
import ApiGruppe from "../../api/lehrveranstaltung/gruppe.js";
|
||||
import ApiStudiengangTree from "../../api/lehrveranstaltung/studiengangtree.js";
|
||||
import ApiSearchbar from "../../api/factory/searchbar.js";
|
||||
import AppConfig from "../AppConfig.js";
|
||||
import ApiLvConfig from "../../api/lehrveranstaltung/config.js";
|
||||
|
||||
|
||||
export default {
|
||||
@@ -18,23 +20,23 @@ export default {
|
||||
components: {
|
||||
CoreSearchbar,
|
||||
VerticalSplit,
|
||||
HorizontalSplit,
|
||||
AppMenu,
|
||||
StvVerband,
|
||||
BaseTreemenu,
|
||||
StvStudiensemester,
|
||||
LvTable,
|
||||
LvTabs,
|
||||
AppConfig,
|
||||
NavLanguage
|
||||
},
|
||||
props: {
|
||||
avatarUrl: String,
|
||||
logoutUrl: String,
|
||||
defaultSemester: String,
|
||||
lvRoot: String,
|
||||
permissions: Object,
|
||||
config: Object,
|
||||
stg: { type: String, required: false },
|
||||
semester: { type: [Number, String], required: false, default: null },
|
||||
studiensemester_kurzbz: { type: String, required: false, default: null },
|
||||
emp: { type: String, required: false, default: null }
|
||||
},
|
||||
|
||||
provide() {
|
||||
return {
|
||||
currentSemester: Vue.computed(() => this.selectedStudiensemester),
|
||||
@@ -44,46 +46,27 @@ export default {
|
||||
lehreinheitAnmerkungDefault: (this.config.lehreinheitAnmerkungDefault || '').replace(/\\n/g, '\n'),
|
||||
lehreinheitRaumtypDefault: this.config.lehreinheitRaumtypDefault,
|
||||
lehreinheitRaumtypAlternativeDefault: this.config.lehreinheitRaumtypAlternativeDefault,
|
||||
|
||||
permissionLehrveranstaltung: this.permissions['lehre/lehrveranstaltung'],
|
||||
permissionGruppenEntfernen: this.permissions['lv-plan/gruppenentfernen'],
|
||||
permissionLektorEntfernen: this.permissions['lv-plan/lektorentfernen'],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateFilter();
|
||||
},
|
||||
watch: {
|
||||
stg() {
|
||||
this.updateFilter();
|
||||
},
|
||||
semester() {
|
||||
this.updateFilter();
|
||||
},
|
||||
selectedStudiensemester() {
|
||||
this.updateFilter();
|
||||
},
|
||||
emp() {
|
||||
this.updateFilter();
|
||||
},
|
||||
studiensemester_kurzbz(newVal) {
|
||||
this.selectedStudiensemester = newVal ?? this.defaultSemester;
|
||||
language: Vue.computed(() => this.$p.user_language),
|
||||
isMobile: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sidebarCollapsed: false,
|
||||
appconfig:{},
|
||||
configEndpoints: ApiLvConfig,
|
||||
selected: [],
|
||||
studiengang: "",
|
||||
filter: {},
|
||||
selectedStudiensemester: this.studiensemester_kurzbz ?? this.defaultSemester,
|
||||
endpoint: ApiStudiengangTree,
|
||||
studiengang_kz: null,
|
||||
selectedStudiensemester: this.defaultSemester,
|
||||
dropdowns: {
|
||||
studiensemester_array: [],
|
||||
sprachen_array: [],
|
||||
lehrform_array: [],
|
||||
raumtyp_array: [],
|
||||
},
|
||||
selectedStudiengang: '',
|
||||
searchbaroptions: {
|
||||
origin: 'lvverwaltung',
|
||||
cssclass: "position-relative",
|
||||
@@ -107,28 +90,112 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filter() {
|
||||
let filter = {
|
||||
emp: this.$route.params.emp,
|
||||
studiensemester_kurzbz: this.selectedStudiensemester
|
||||
};
|
||||
let index;
|
||||
if (this.$route.params.treemenu) {
|
||||
index = this.$route.params.treemenu.indexOf('stg');
|
||||
if (index > -1)
|
||||
filter.stg = this.$route.params.treemenu[index+1];
|
||||
index = this.$route.params.treemenu.indexOf('semester');
|
||||
if (index > -1)
|
||||
filter.semester = this.$route.params.treemenu[index+1];
|
||||
}
|
||||
filter.activeFilter = filter.emp ? 'employee' : filter.stg ? 'verband' : null;
|
||||
return filter;
|
||||
},
|
||||
emp() {
|
||||
return this.filter.emp;
|
||||
},
|
||||
stg() {
|
||||
return this.filter.stg;
|
||||
},
|
||||
semester() {
|
||||
return this.filter.semester;
|
||||
},
|
||||
appMenuExtraItems()
|
||||
{
|
||||
let extraItems = [];
|
||||
|
||||
const studiengang_kz = this.studiengang_kz || '';
|
||||
const studiensemester = this.selectedStudiensemester;
|
||||
const semester = this.semester || '';
|
||||
const uid = this.emp || '';
|
||||
|
||||
|
||||
extraItems.push(
|
||||
{
|
||||
description: 'lehre/berichte',
|
||||
requires: ['studiengang_kz'],
|
||||
children: [
|
||||
{
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'content/statistik/lvplanung.xls.php'
|
||||
+ '?studiengang_kz=' + studiengang_kz
|
||||
+ '&studiensemester_kurzbz=' + studiensemester
|
||||
+ '&semester=' + semester,
|
||||
description: 'lehre/lvplanung',
|
||||
requires: ['studiengang_kz']
|
||||
},
|
||||
{
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'content/statistik/lehrauftragsliste_gst.xls.php'
|
||||
+ '?studiengang_kz=' + studiengang_kz
|
||||
+ '&studiensemester_kurzbz=' + studiensemester
|
||||
+ '&semester=' + semester,
|
||||
description: 'lehre/lehrauftragsliste',
|
||||
requires: ['studiengang_kz']
|
||||
},
|
||||
{
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'content/pdfExport.php?xml=lehrauftrag.xml.php'
|
||||
+ '&xsl=Lehrauftrag'
|
||||
+ '&stg_kz=' + studiengang_kz
|
||||
+ '&ss=' + studiensemester,
|
||||
description: 'lehre/lehrauftraege',
|
||||
requires: ['studiengang_kz']
|
||||
},
|
||||
{
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'content/pdfExport.php?xml=lehrauftrag.xml.php'
|
||||
+ '&xsl=Lehrauftrag'
|
||||
+ '&stg_kz=' + studiengang_kz
|
||||
+ '&ss=' + studiensemester
|
||||
+ '&uid=' + uid,
|
||||
description: 'lehre/lehrauftragslisteemp',
|
||||
requires: ['emp']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'vilesci/lehre/lehrveranstaltung.php'
|
||||
+ '?stg_kz=' + studiengang_kz,
|
||||
description: 'lehre/extrakvverwaltung',
|
||||
requires: ['studiengang_kz']
|
||||
}
|
||||
);
|
||||
|
||||
return extraItems;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
sidebarCollapsed(newVal) {
|
||||
if(newVal)
|
||||
this.$refs.hSplit.collapseLeft()
|
||||
else
|
||||
this.$refs.hSplit.showBoth()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateFilter()
|
||||
{
|
||||
const filter = {
|
||||
stg: this.stg,
|
||||
emp: this.emp,
|
||||
semester: this.semester,
|
||||
studiensemester_kurzbz: this.selectedStudiensemester,
|
||||
activeFilter: this.emp ? 'employee' : this.stg ? 'verband' : null
|
||||
}
|
||||
|
||||
if (this.stg !== undefined)
|
||||
{
|
||||
this.selectedStudiengang = this.semester !== '' && this.semester
|
||||
? `${this.stg}/${this.semester}`
|
||||
: this.stg;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.selectedStudiengang = '';
|
||||
}
|
||||
this.filter = filter;
|
||||
// deprecated
|
||||
},
|
||||
handleRowClicked(data)
|
||||
{
|
||||
@@ -136,77 +203,107 @@ export default {
|
||||
},
|
||||
onSelectEmployee(emp)
|
||||
{
|
||||
const { stg, semester } = this.filter;
|
||||
|
||||
let studiensemester_kurzbz = this.selectedStudiensemester;
|
||||
const params = { emp };
|
||||
|
||||
if (stg)
|
||||
params.stg = stg;
|
||||
if (semester !== null)
|
||||
params.semester = semester;
|
||||
if (studiensemester_kurzbz)
|
||||
params.studiensemester_kurzbz = studiensemester_kurzbz;
|
||||
|
||||
this.$router.push({ name: 'byEmp', params })
|
||||
this.$router.push({
|
||||
name: 'emp',
|
||||
params: {
|
||||
...this.$route.params,
|
||||
emp
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onSelectVerband({link})
|
||||
onSelectVerband({ path: link, stg_kz })
|
||||
{
|
||||
let stg = null;
|
||||
let semester = null;
|
||||
let studiensemester_kurzbz = this.selectedStudiensemester;
|
||||
|
||||
if (typeof link === 'number')
|
||||
stg = link;
|
||||
else if (typeof link === 'string')
|
||||
{
|
||||
[stg, semester] = link.split('/');
|
||||
}
|
||||
|
||||
const routeName = this.filter.emp ? 'byEmp' : 'byStg';
|
||||
const params = { stg };
|
||||
|
||||
params.semester = '';
|
||||
if (semester !== null)
|
||||
params.semester = semester;
|
||||
if (studiensemester_kurzbz)
|
||||
params.studiensemester_kurzbz = studiensemester_kurzbz;
|
||||
if (this.filter.emp)
|
||||
params.emp = this.filter.emp;
|
||||
this.$router.push({ name: routeName, params });
|
||||
this.$router.push({
|
||||
name: this.$route.name == 'emp' ? 'emp' : 'treemenu',
|
||||
params: {
|
||||
...this.$route.params,
|
||||
treemenu: link.split('/')
|
||||
}
|
||||
});
|
||||
this.selected = [];
|
||||
this.studiengang_kz = stg_kz;
|
||||
},
|
||||
resetEmployeeFilter()
|
||||
{
|
||||
const newParams = { ...this.filter, activeFilter: 'verband' };
|
||||
if (newParams.stg === '')
|
||||
this.$router.replace({ name: 'index' });
|
||||
else
|
||||
{
|
||||
delete newParams.emp;
|
||||
|
||||
this.$router.replace({ name: 'byStg', params: newParams });
|
||||
}
|
||||
let params = { ...this.$route.params };
|
||||
|
||||
delete params.emp;
|
||||
|
||||
this.$router.replace({
|
||||
name: params.treemenu ? 'treemenu' : 'stdsem',
|
||||
params
|
||||
});
|
||||
},
|
||||
resetStgFilter()
|
||||
{
|
||||
const newParams = { ...this.filter, activeFilter: 'emp' };
|
||||
delete newParams.stg;
|
||||
this.selectedStudiengang = '';
|
||||
this.$router.replace({ name: 'byEmp', params: newParams });
|
||||
let params = { ...this.$route.params };
|
||||
let index = params.treemenu.indexOf('stg');
|
||||
if (index > -1)
|
||||
params.treemenu = params.treemenu.slice(0, index);
|
||||
if (params.treemenu.length) {
|
||||
this.$router.replace({
|
||||
params: {
|
||||
treemenu: params.treemenu
|
||||
}
|
||||
});
|
||||
} else {
|
||||
delete params.treemenu;
|
||||
this.$router.replace({
|
||||
name: params.emp ? 'emp' : 'stdsem',
|
||||
params
|
||||
});
|
||||
}
|
||||
},
|
||||
searchfunction(params) {
|
||||
return this.$api.call(ApiSearchbar.search(params));
|
||||
searchfunction(params, config) {
|
||||
return this.$api.call(ApiSearchbar.search(params), config);
|
||||
},
|
||||
studiensemesterChanged(newValue) {
|
||||
const routeName = this.filter.activeFilter === 'employee' ? 'byEmp' : 'byStg';
|
||||
const newParams = {...this.filter, studiensemester_kurzbz: newValue};
|
||||
this.$router.push({ name: routeName, params: newParams });
|
||||
studiensemesterChanged(stdsem) {
|
||||
if (!stdsem) {
|
||||
// no valid studiensemester in url
|
||||
this.$router.replace({
|
||||
params: {
|
||||
stdsem: this.defaultSemester.toLowerCase()
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (stdsem.toLowerCase() != this.selectedStudiensemester?.toLowerCase()) {
|
||||
this.$router.push({
|
||||
params: {
|
||||
stdsem: stdsem.toLowerCase()
|
||||
}
|
||||
});
|
||||
}
|
||||
this.selectedStudiensemester = stdsem;
|
||||
this.selected = [];
|
||||
},
|
||||
isDisabled(item)
|
||||
{
|
||||
if (!item?.requires?.length)
|
||||
return false;
|
||||
|
||||
const values = {
|
||||
studiengang_kz: this.studiengang_kz,
|
||||
stg: this.stg,
|
||||
emp: this.emp,
|
||||
studiensemester: this.selectedStudiensemester,
|
||||
semester: this.semester
|
||||
};
|
||||
|
||||
return item.requires.some(req => !values[req]);
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.$route.params.stdsem) {
|
||||
this.$router.replace({
|
||||
name: 'stdsem',
|
||||
params: {
|
||||
stdsem: this.defaultSemester.toLowerCase()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.$p.loadCategory(['lehre', 'person', 'global'])
|
||||
|
||||
this.$api.call(ApiDetails.getStudiensemester())
|
||||
@@ -239,9 +336,8 @@ export default {
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
|
||||
template: /* html */`
|
||||
<div class="stv">
|
||||
<div class="stv" :class="{ 'sidebar-collapsed': sidebarCollapsed }">
|
||||
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
|
||||
<div class="col-md-4 col-lg-3 col-xl-2 d-flex align-items-center">
|
||||
<button
|
||||
@@ -269,6 +365,51 @@ export default {
|
||||
<span class="fa-solid fa-table-list"></span>
|
||||
</button>
|
||||
<core-searchbar :searchoptions="searchbaroptions" :searchfunction=searchfunction class="searchbar w-100"></core-searchbar>
|
||||
<div id="nav-user" class="dropdown">
|
||||
<button
|
||||
id="nav-user-btn"
|
||||
class="btn btn-link rounded-0 py-0"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-target="#nav-user-menu"
|
||||
aria-expanded="false"
|
||||
aria-controls="nav-user-menu"
|
||||
>
|
||||
<img
|
||||
:src="avatarUrl"
|
||||
:alt="$p.t('profilUpdate/profilBild')"
|
||||
class="bg-light avatar rounded-circle border border-light"
|
||||
/>
|
||||
</button>
|
||||
<ul
|
||||
ref="navUserDropdown"
|
||||
class="dropdown-menu dropdown-menu-dark dropdown-menu-end rounded-0 text-center m-0"
|
||||
aria-labelledby="nav-user-btn"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-item"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#configModal"
|
||||
>
|
||||
{{ $p.t('ui/settings') }}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider m-0"/></li>
|
||||
<li>
|
||||
<nav-language
|
||||
item-class="dropdown-item border-left-dark"
|
||||
/>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider m-0"/></li>
|
||||
<li>
|
||||
<a class="dropdown-item" :href="logoutUrl">
|
||||
{{ $p.t('ui/logout') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container-fluid overflow-hidden">
|
||||
<div class="row h-100">
|
||||
@@ -278,54 +419,99 @@ export default {
|
||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<app-menu app-identifier="lvv" />
|
||||
<app-menu app-identifier="lvv">
|
||||
<template v-for="(item, key) in appMenuExtraItems" :key="key">
|
||||
<li v-if="item.children" class="dropend">
|
||||
<a
|
||||
class="dropdown-toggle"
|
||||
href="#"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-bs-popper-config='{"strategy":"fixed"}'
|
||||
>
|
||||
{{ $p.t(item.description) }}
|
||||
</a>
|
||||
<ul class="dropdown-menu p-0">
|
||||
<li
|
||||
v-for="(child, childKey) in item.children"
|
||||
:key="childKey"
|
||||
>
|
||||
<a class="dropdown-item" :href="child.link" target="_blank" :class="{ disabled: isDisabled(child) }">
|
||||
{{ $p.t(child.description) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-else>
|
||||
<a
|
||||
:href="item.link"
|
||||
target="_blank"
|
||||
:class="{ disabled: isDisabled(item) }">
|
||||
{{ $p.t(item.description) }}
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
</app-menu>
|
||||
</div>
|
||||
</aside>
|
||||
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
|
||||
<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 :preselectedKey="selectedStudiengang" :endpoint="endpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
|
||||
<stv-studiensemester v-model:studiensemester-kurzbz="selectedStudiensemester" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
|
||||
</nav>
|
||||
|
||||
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
|
||||
<vertical-split>
|
||||
<template #top>
|
||||
<lv-table ref="lvTable"
|
||||
v-model:selected="selected"
|
||||
@row-clicked="handleRowClicked"
|
||||
:filter="filter"
|
||||
>
|
||||
<template #filterzuruecksetzen v-if="filter.activeFilter === 'employee'">
|
||||
<span class="fw-bold small">
|
||||
[{{ $p.t('lehre', 'lektor') }}: {{ filter.emp || '' }}
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary btn-action btn-sm ms-1"
|
||||
:title="$p.t('ui', 'filterdelete')"
|
||||
@click="resetEmployeeFilter">
|
||||
<i class="fa fa-xmark"></i>
|
||||
</button>
|
||||
<template v-if="filter.stg">
|
||||
| Stg: {{ filter.stg }}
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary btn-action btn-sm ms-1"
|
||||
:title="$p.t('ui', 'filterdelete')"
|
||||
@click="resetStgFilter">
|
||||
<i class="fa fa-xmark"></i>
|
||||
</button>
|
||||
</template>
|
||||
]
|
||||
</span>
|
||||
<horizontal-split ref="hSplit" :defaultRatio="[15, 85]">
|
||||
<template #left>
|
||||
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 w-100">
|
||||
<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>
|
||||
<div class="overflow-auto col h-0">
|
||||
<base-treemenu
|
||||
config="lvv"
|
||||
:preselected-key="$route.params.treemenu"
|
||||
@select-entry="onSelectVerband"
|
||||
></base-treemenu>
|
||||
</div>
|
||||
<stv-studiensemester :studiensemester-kurzbz="$route.params.stdsem || defaultSemester" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
|
||||
</nav>
|
||||
</template>
|
||||
<template #right>
|
||||
<main>
|
||||
<vertical-split>
|
||||
<template #top>
|
||||
<lv-table ref="lvTable"
|
||||
v-model:selected="selected"
|
||||
@row-clicked="handleRowClicked"
|
||||
:filter="filter"
|
||||
>
|
||||
<template #filterzuruecksetzen v-if="filter.activeFilter === 'employee'">
|
||||
<span class="fw-bold small">
|
||||
[{{ $p.t('lehre', 'lektor') }}: {{ filter.emp || '' }}
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary btn-action btn-sm ms-1"
|
||||
:title="$p.t('ui', 'filterdelete')"
|
||||
@click="resetEmployeeFilter">
|
||||
<i class="fa fa-xmark"></i>
|
||||
</button>
|
||||
<template v-if="filter.stg">
|
||||
| Stg: {{ filter.stg.toUpperCase() }}
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary btn-action btn-sm ms-1"
|
||||
:title="$p.t('ui', 'filterdelete')"
|
||||
@click="resetStgFilter">
|
||||
<i class="fa fa-xmark"></i>
|
||||
</button>
|
||||
</template>
|
||||
]
|
||||
</span>
|
||||
</template>
|
||||
</lv-table>
|
||||
</template>
|
||||
</lv-table>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<lv-tabs ref="details" :lv="selected"></lv-tabs>
|
||||
</template>
|
||||
</vertical-split>
|
||||
</main>
|
||||
<template #bottom>
|
||||
<lv-tabs ref="details" :lv="selected"></lv-tabs>
|
||||
</template>
|
||||
</vertical-split>
|
||||
</main>
|
||||
</template>
|
||||
</horizontal-split>
|
||||
</div>
|
||||
</div>
|
||||
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
|
||||
</div>`
|
||||
};
|
||||
|
||||
@@ -140,7 +140,7 @@ export default{
|
||||
{
|
||||
this.$emit('changedLektor', this.changed.mitarbeiter_uid);
|
||||
}
|
||||
if (this.changed.semesterstunden || this.changed.stundensatz)
|
||||
else if (this.changed.semesterstunden || this.changed.stundensatz)
|
||||
{
|
||||
this.$emit('changedCosts');
|
||||
}
|
||||
@@ -183,9 +183,6 @@ export default{
|
||||
},
|
||||
|
||||
},
|
||||
created() {
|
||||
this.getLektorData()
|
||||
},
|
||||
template: `
|
||||
<core-form ref="form" @submit.prevent="updateDaten">
|
||||
<div class="position-sticky top-0 z-1">
|
||||
|
||||
@@ -49,6 +49,52 @@ export default{
|
||||
{title: this.$p.t('person', 'nachname'), field:"nachname"},
|
||||
{title: this.$p.t('person', 'vorname'), field:"vorname"},
|
||||
{title: this.$p.t('person', 'uid'), field:"mitarbeiter_uid"},
|
||||
{title: this.$p.t('global/insertvon'), field: 'insertvon', visible: false},
|
||||
{
|
||||
title: this.$p.t('global/insertamum'),
|
||||
field: "insertamum",
|
||||
visible: false,
|
||||
formatter: function (cell) {
|
||||
const dateStr = cell.getValue();
|
||||
|
||||
if (!dateStr) return "";
|
||||
|
||||
const date = new Date(dateStr);
|
||||
|
||||
return date.toLocaleString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false
|
||||
});
|
||||
}
|
||||
},
|
||||
{title: this.$p.t('global/updatevon'), field: 'updatevon', visible: false},
|
||||
{
|
||||
title: this.$p.t('global/updateamum'),
|
||||
field: "updateamum",
|
||||
visible: false,
|
||||
formatter: function (cell) {
|
||||
const dateStr = cell.getValue();
|
||||
|
||||
if (!dateStr) return "";
|
||||
|
||||
const date = new Date(dateStr);
|
||||
|
||||
return date.toLocaleString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false
|
||||
});
|
||||
}
|
||||
},
|
||||
{title: this.$p.t('lehre', 'lehreinheit_id'), field:"lehreinheit_id"},
|
||||
{
|
||||
title: this.$p.t('lehre', 'verplant'),
|
||||
@@ -102,7 +148,7 @@ export default{
|
||||
selectable: true,
|
||||
selectableRangeMode: 'click',
|
||||
selectableRows:1,
|
||||
persistenceID: 'lehrveranstaltungen_lektor_table_2025_05_27_v1',
|
||||
persistenceID: 'lehrveranstaltungen_lektor_table_2026_09_11_v1',
|
||||
}},
|
||||
},
|
||||
mounted(){
|
||||
|
||||
@@ -2,14 +2,25 @@ import {CoreFilterCmpt} from "../../filter/Filter.js";
|
||||
import BsModal from "../../Bootstrap/Modal.js";
|
||||
import DetailsForm from "../Details/Form.js";
|
||||
import CoreTag from '../../Tag/Tag.js';
|
||||
import { tagHeaderFilter } from '../../../../js/tabulator/filters/extendedHeaderFilter.js';
|
||||
import { extendedHeaderFilter } from "../../../../js/tabulator/filters/extendedHeaderFilter.js";
|
||||
import {
|
||||
buildTagHeaderFilterExpression,
|
||||
buildTagOptionsFromRows,
|
||||
customTagFilter,
|
||||
setTagHeaderFilterValue,
|
||||
tagHeaderFilter,
|
||||
extendedHeaderFilter,
|
||||
syncTagHeaderFilterOptions,
|
||||
syncSelectedTagOptionsWithHeaderFilters
|
||||
} from "../../../../js/tabulator/filters/extendedHeaderFilter.js";
|
||||
import { tagFormatter } from "../../../../js/tabulator/formatter/tags.js";
|
||||
import { addTagInTable, deleteTagInTable, updateTagInTable } from "../../../../js/helpers/TagHelper.js";
|
||||
|
||||
import ApiLv from "../../../api/lehrveranstaltung.js";
|
||||
import ApiTag from "../../../api/lehrveranstaltung/tag.js";
|
||||
import ApiLehreinheit from "../../../api/lehrveranstaltung/lehreinheit.js";
|
||||
import ApiTreemenu from "../../../api/factory/treemenu.js";
|
||||
|
||||
import LvvColumns from '../../../../../index.ci.php/js/tabulatorcolumns/lvverwaltung';
|
||||
|
||||
export default {
|
||||
name: "LVVerwaltungTable",
|
||||
@@ -54,6 +65,7 @@ export default {
|
||||
if (this.$refs.table && this.$refs.table.tabulator)
|
||||
{
|
||||
this.expanded = [];
|
||||
this.currentTreeLevel = 0;
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
@@ -65,7 +77,20 @@ export default {
|
||||
this.lv_info_default.studiensemester_kurzbz = newVal
|
||||
this.lv_info = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
"tagFilterState.selectedOptions": {
|
||||
handler() {
|
||||
const selectedOptions = this.tagFilterState.selectedOptions;
|
||||
const combinedFilterStatement =
|
||||
buildTagHeaderFilterExpression(selectedOptions);
|
||||
|
||||
setTagHeaderFilterValue(
|
||||
combinedFilterStatement,
|
||||
this.$refs.table.tabulator
|
||||
);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -96,8 +121,37 @@ export default {
|
||||
handler: (data) => {
|
||||
this.getExpandedRows()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
event: "dataLoaded",
|
||||
handler: (data) => {
|
||||
syncTagHeaderFilterOptions(
|
||||
Array.isArray(data) ? data : [],
|
||||
this.tagFilterState.initialOptions,
|
||||
this.tagFilterState.selectedOptions,
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
event: "dataFiltered",
|
||||
handler: (filters, rows) => {
|
||||
syncSelectedTagOptionsWithHeaderFilters(
|
||||
filters,
|
||||
this.tagFilterState.selectedOptions,
|
||||
this.tagsEnabled,
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
event: "columnWidth",
|
||||
handler: (column) => {
|
||||
if (column.getField() !== "tags") return;
|
||||
|
||||
column.getCells().forEach((cell) => {
|
||||
cell.getElement().firstElementChild?.fitTags?.();
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
formData: {},
|
||||
lv_info: false,
|
||||
@@ -111,7 +165,20 @@ export default {
|
||||
raumtypalternativ: this.lehreinheitRaumtypAlternativeDefault,
|
||||
lehrfach_id: ''
|
||||
|
||||
}
|
||||
},
|
||||
tagFilterState: {
|
||||
initialOptions: [],
|
||||
selectedOptions: [],
|
||||
},
|
||||
tagFilterLabels: {
|
||||
tag: "Tag",
|
||||
clear: "Clear",
|
||||
connectors: {
|
||||
AND: "AND",
|
||||
OR: "OR",
|
||||
NOT: "NOT",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -122,7 +189,7 @@ export default {
|
||||
ajaxRequestFunc: async (url, config, params) => {
|
||||
let realUrl = this.buildApiUrl();
|
||||
if (realUrl)
|
||||
return this.$api.call(ApiLv.getTable(this.buildApiUrl()));
|
||||
return this.$api.call(this.buildApiUrl());
|
||||
},
|
||||
ajaxResponse: (url, params, response) => { return response?.data || [] },
|
||||
dataTree: true,
|
||||
@@ -140,7 +207,16 @@ export default {
|
||||
headerFilterFunc: extendedHeaderFilter,
|
||||
},
|
||||
layout: 'fitDataStretch',
|
||||
persistenceID: 'lehrveranstaltungen_2025_07_31_v1',
|
||||
height: '100%',
|
||||
persistenceID: 'lehrveranstaltungen_2026_07_15_v1',
|
||||
persistence: {
|
||||
sort: true,
|
||||
columns: ["width", "visible"],
|
||||
filter: false,
|
||||
headerFilter: false,
|
||||
group: false,
|
||||
page: false,
|
||||
},
|
||||
selectableRowsRangeMode: 'click',
|
||||
selectableRows: true,
|
||||
rowContextMenu: (component, e) => {
|
||||
@@ -190,91 +266,35 @@ export default {
|
||||
];
|
||||
},
|
||||
|
||||
columns: [
|
||||
{
|
||||
title: this.$p.t('lehre', 'kurzbz'),
|
||||
field: "lv_kurzbz",
|
||||
headerFilterFuncParams: {field: 'lv_kurzbz'},
|
||||
headerFilter: true,
|
||||
formatter: (cell, formatterParams) => {
|
||||
const rowData = cell.getRow().getData();
|
||||
const iconKey = (rowData.lehrtyp_kurzbz || '').toLowerCase();
|
||||
const lvkurzbz = (cell.getValue()).toUpperCase();
|
||||
|
||||
const parentspan = document.createElement('span');
|
||||
const span = document.createElement('span');
|
||||
|
||||
|
||||
span.classList.add('lv_table_icon', `icon-${iconKey}`);
|
||||
span.title = iconKey || 'LV-Teil';
|
||||
|
||||
parentspan.appendChild(span);
|
||||
parentspan.appendChild(document.createTextNode(` ${lvkurzbz}`));
|
||||
|
||||
return parentspan
|
||||
},
|
||||
|
||||
cellClick: (e, cell) => {
|
||||
cell.getRow().treeToggle();
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
field: 'tags',
|
||||
tooltip: false,
|
||||
headerFilter: "input",
|
||||
headerFilterFunc: tagHeaderFilter,
|
||||
headerFilterFuncParams: {field: 'tags'},
|
||||
formatter: (cell) => tagFormatter(cell, this.$refs.tagComponent),
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: this.$p.t('lehre', 'lehrveranstaltung_id'),
|
||||
field: "lehrveranstaltung_id",
|
||||
headerFilterFuncParams: {field: 'lehrveranstaltung_id'},
|
||||
headerFilter: true,
|
||||
visible: false
|
||||
},
|
||||
{title: this.$p.t('ui', 'bezeichnung'), field: "lv_bezeichnung", headerFilter: true, headerFilterFuncParams: {field: 'lv_bezeichnung'}},
|
||||
{title: this.$p.t('lehre', 'bezeichnungeng'), field: "lv_bezeichnung_english", headerFilter: true, headerFilterFuncParams: {field: 'lv_bezeichnung_english'}, visible: false},
|
||||
{
|
||||
title: this.$p.t('lehre', 'studiengangskennzahlLehre'),
|
||||
field: "lv_studiengang_kz",
|
||||
headerFilter: true,
|
||||
headerFilterFuncParams: {field: 'lv_studiengang_kz'},
|
||||
visible: false
|
||||
},
|
||||
{title: this.$p.t('lehre', 'studiengang'), field: "studiengang", headerFilter: true, headerFilterFuncParams: {field: 'studiengang'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'semester'), field: "semester", headerFilter: true, headerFilterFuncParams: {field: 'semester'}},
|
||||
{title: this.$p.t('global', 'sprache'), field: "sprache", headerFilter: true, headerFilterFuncParams: {field: 'sprache'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'ects'), field: "lv_ects", headerFilter: true, headerFilterFuncParams: {field: 'lv_ects'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'semesterstunden'), field: "semesterstunden", headerFilter: true, headerFilterFuncParams: {field: 'semesterstunden'}, visible: false},
|
||||
{title: this.$p.t('global', 'anmerkung'), field: "anmerkung", headerFilter: true, headerFilterFuncParams: {field: 'anmerkung'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'lehre'), field: "lehre", headerFilter: true, headerFilterFuncParams: {field: 'lehre'}, formatter: (cell) => cell.getValue() ? this.toUpperCase(this.$p.t('ui', 'ja')) : this.toUpperCase(this.$p.t('ui', 'nein')), visible: false},
|
||||
{title: this.$p.t('person', 'aktiv'), field: "aktiv", headerFilter: true, headerFilterFuncParams: {field: 'aktiv'}, formatter: (cell) => cell.getValue() ? this.toUpperCase(this.$p.t('ui', 'ja')) : this.toUpperCase(this.$p.t('ui', 'nein')), visible: false},
|
||||
{title: this.$p.t('lehre', 'organisationsform'), field: "orgform_kurzbz", headerFilter: true, headerFilterFuncParams: {field: 'orgform_kurzbz'}},
|
||||
{title: this.$p.t('ui', 'studienplan_id'), field: "studienplan_id", headerFilter: true, headerFilterFuncParams: {field: 'studienplan_id'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'studienplan'), field: "studienplan_bezeichnung", headerFilter: true, headerFilterFuncParams: {field: 'studienplan_bezeichnung'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'lehrtyp'), field: "lehrtyp_kurzbz", headerFilter: true, headerFilterFuncParams: {field: 'lehrtyp_kurzbz'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'lehrform'), field: "lehrform_kurzbz", headerFilter: true, headerFilterFuncParams: {field: 'lehrform_kurzbz'}},
|
||||
{title: this.$p.t('lehre', 'leplanstunden'), field: "le_planstunden", headerFilter: true, headerFilterFuncParams: {field: 'le_planstunden'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'lehreinheit_id'), field: "lehreinheit_id", headerFilter: true, headerFilterFuncParams: {field: 'lehreinheit_id'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'studiensemester'), field: "studiensemester_kurzbz", headerFilter: true, headerFilterFuncParams: {field: 'studiensemester_kurzbz'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'unr'), field: "unr", headerFilter: true, headerFilterFuncParams: {field: 'unr'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'organisationseinheit'), field: "fachbereich", headerFilter: true, headerFilterFuncParams: {field: 'fachbereich'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'stundenblockung'), field: "stundenblockung", headerFilter: true, headerFilterFuncParams: {field: 'stundenblockung'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'wochenrhythmus'), field: "wochenrythmus", headerFilter: true, headerFilterFuncParams: {field: 'wochenrythmus'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'startkw'), field: "start_kw", headerFilter: true, headerFilterFuncParams: {field: 'startkw'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'raumtyp'), field: "raumtyp", headerFilter: true, headerFilterFuncParams: {field: 'raumtyp'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'raumtypalternativ'), field: "raumtypalternativ", headerFilter: true, headerFilterFuncParams: {field: 'raumtypalternativ'}, visible: false},
|
||||
{title: this.$p.t('lehre', 'gruppen'), field: "gruppen", headerFilter: true, headerFilterFuncParams: {field: 'gruppen'}},
|
||||
{title: this.$p.t('lehre', 'lehrende'), field: "lektoren", headerFilter: true, headerFilterFuncParams: {field: ['lektoren', 'vorname', 'nachname']}},
|
||||
],
|
||||
columns: LvvColumns,
|
||||
locale: true
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
Tabulator.extendModule("format", "formatters", {
|
||||
tagHeaderFilter,
|
||||
tagFormatter: (cell, params, onRendered) => tagFormatter(cell, this.$refs.tagComponent, onRendered),
|
||||
ja_nein: cell => cell.getValue()
|
||||
? this.toUpperCase(this.$p.t('ui', 'ja'))
|
||||
: this.toUpperCase(this.$p.t('ui', 'nein'))
|
||||
});
|
||||
LvvColumns.forEach(col => {
|
||||
if (col.headerFilter == 'input' && col.headerFilterFunc == 'tagHeaderFilter') {
|
||||
col.headerFilter = customTagFilter;
|
||||
col.headerFilterParams = {
|
||||
listOnEmpty: true,
|
||||
autocomplete: true,
|
||||
sort: "asc",
|
||||
initialOptions: this.tagFilterState.initialOptions,
|
||||
selectedOptions: this.tagFilterState.selectedOptions,
|
||||
labels: this.tagFilterLabels,
|
||||
};
|
||||
col.headerSort = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
if (this.shouldAutoLoad())
|
||||
{
|
||||
@@ -295,7 +315,8 @@ export default {
|
||||
|
||||
if (this.shouldAutoLoad)
|
||||
{
|
||||
this.$refs.table.reloadTable();
|
||||
if (this.$refs.table.tabulator)
|
||||
this.$refs.table.reloadTable();
|
||||
}
|
||||
},
|
||||
rowSelectionChanged(data) {
|
||||
@@ -304,14 +325,13 @@ export default {
|
||||
|
||||
if (data[0]?.lehreinheit_id !== undefined && this.selectedColumnValues.length === 1)
|
||||
{
|
||||
this.$emit('update:selected', [data[0]]);
|
||||
this.lv_info = false
|
||||
}
|
||||
else if (data[0]?.lehrveranstaltung_id)
|
||||
{
|
||||
this.$emit('update:selected', {});
|
||||
this.getLVInfos(data[0]);
|
||||
}
|
||||
this.$emit('update:selected', data);
|
||||
},
|
||||
getLVInfos(data)
|
||||
{
|
||||
@@ -335,17 +355,20 @@ export default {
|
||||
},
|
||||
buildApiUrl()
|
||||
{
|
||||
if (this.filter.activeFilter === 'employee' && this.filter.emp)
|
||||
{
|
||||
const { emp, stg, studiensemester_kurzbz } = this.filter;
|
||||
return this.$api.getUri(ApiLv.getByEmp(studiensemester_kurzbz, emp, stg));
|
||||
if (!['emp', 'treemenu'].includes(this.$route.name))
|
||||
return null;
|
||||
|
||||
let url = 'stdsem/' + this.$route.params.stdsem;
|
||||
|
||||
if (this.$route.params.emp) {
|
||||
url += '/emp/' + this.$route.params.emp;
|
||||
}
|
||||
|
||||
if (this.filter.activeFilter === 'verband' && this.filter.stg)
|
||||
{
|
||||
const { stg, semester, studiensemester_kurzbz } = this.filter;
|
||||
return this.$api.getUri(ApiLv.getByStg(studiensemester_kurzbz, stg, semester));
|
||||
if (this.$route.params.treemenu) {
|
||||
url += '/' + this.$route.params.treemenu.join('/');
|
||||
}
|
||||
|
||||
return ApiTreemenu.data('lvverwaltung', url);
|
||||
},
|
||||
resetEmployeeFilter()
|
||||
{
|
||||
@@ -381,16 +404,31 @@ export default {
|
||||
},
|
||||
addedTag(addedTag)
|
||||
{
|
||||
addTagInTable(addedTag, this.allRows, 'lehreinheit_id')
|
||||
addTagInTable(addedTag, this.allRows, 'lehreinheit_id');
|
||||
syncTagHeaderFilterOptions(
|
||||
this.$refs.table?.tabulator?.getData() || [],
|
||||
this.tagFilterState.initialOptions,
|
||||
this.tagFilterState.selectedOptions,
|
||||
);
|
||||
},
|
||||
deletedTag(deletedTag)
|
||||
{
|
||||
deleteTagInTable(deletedTag, this.allRows);
|
||||
syncTagHeaderFilterOptions(
|
||||
this.$refs.table?.tabulator?.getData() || [],
|
||||
this.tagFilterState.initialOptions,
|
||||
this.tagFilterState.selectedOptions,
|
||||
);
|
||||
},
|
||||
|
||||
updatedTag(updatedTag)
|
||||
{
|
||||
updateTagInTable(updatedTag, this.allRows);
|
||||
syncTagHeaderFilterOptions(
|
||||
this.$refs.table?.tabulator?.getData() || [],
|
||||
this.tagFilterState.initialOptions,
|
||||
this.tagFilterState.selectedOptions,
|
||||
);
|
||||
},
|
||||
async copyLehreinheit(row, art)
|
||||
{
|
||||
@@ -518,47 +556,50 @@ export default {
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<core-filter-cmpt
|
||||
ref="table"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
:tabulator-events="tabulatorEvents"
|
||||
table-only
|
||||
:side-menu="false"
|
||||
:reload=true
|
||||
new-btn-label="LV-Teil hinzufügen"
|
||||
new-btn-show
|
||||
:new-btn-disabled="!lv_info"
|
||||
@click:new="showLehreinheitModal">
|
||||
|
||||
<template #actions>
|
||||
<button @click="expandTree" class="btn btn-outline-secondary" type="button" :title="$p.t('lehre', 'aufklappen')"><i class="fa-solid fa-maximize"></i></button>
|
||||
<button @click="resetTree" class="btn btn-outline-secondary" type="button" :title="$p.t('lehre', 'zuklappen')"><i id="togglegroup" class="fa-solid fa-minimize"></i></button>
|
||||
<core-tag ref="tagComponent"
|
||||
:endpoint="tagEndpoint"
|
||||
:values="selectedColumnValues"
|
||||
@added="addedTag"
|
||||
@deleted="deletedTag"
|
||||
@updated="updatedTag"
|
||||
zuordnung_typ="lehreinheit_id"
|
||||
></core-tag>
|
||||
</template>
|
||||
<template #search>
|
||||
<slot name="filterzuruecksetzen"></slot>
|
||||
</template>
|
||||
</core-filter-cmpt>
|
||||
<bs-modal ref="lehreinheitModal" dialogClass="modal-xxl">
|
||||
<template #title>
|
||||
<p class="fw-bold mt-3">{{$p.t('lehre', 'newlehreinheit')}}</p>
|
||||
</template>
|
||||
|
||||
<template v-if="lv_info">
|
||||
<details-form :data="lv_info"/>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-primary" @click="addNewLehreinheit">{{$p.t('ui', 'speichern')}}</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
|
||||
<div class="lv-list h-100 pt-3">
|
||||
<div class="tabulator-container d-flex flex-column h-100">
|
||||
<core-filter-cmpt
|
||||
ref="table"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
:tabulator-events="tabulatorEvents"
|
||||
table-only
|
||||
:side-menu="false"
|
||||
:reload=true
|
||||
new-btn-label="LV-Teil hinzufügen"
|
||||
new-btn-show
|
||||
:new-btn-disabled="!lv_info"
|
||||
@click:new="showLehreinheitModal">
|
||||
|
||||
<template #actions>
|
||||
<button @click="expandTree" class="btn btn-outline-secondary" type="button" :title="$p.t('lehre', 'aufklappen')"><i class="fa-solid fa-maximize"></i></button>
|
||||
<button @click="resetTree" class="btn btn-outline-secondary" type="button" :title="$p.t('lehre', 'zuklappen')"><i id="togglegroup" class="fa-solid fa-minimize"></i></button>
|
||||
<core-tag ref="tagComponent"
|
||||
:endpoint="tagEndpoint"
|
||||
:values="selectedColumnValues"
|
||||
@added="addedTag"
|
||||
@deleted="deletedTag"
|
||||
@updated="updatedTag"
|
||||
zuordnung_typ="lehreinheit_id"
|
||||
></core-tag>
|
||||
</template>
|
||||
<template #search>
|
||||
<slot name="filterzuruecksetzen"></slot>
|
||||
</template>
|
||||
</core-filter-cmpt>
|
||||
</div>
|
||||
<bs-modal ref="lehreinheitModal" dialogClass="modal-xxl">
|
||||
<template #title>
|
||||
<p class="fw-bold mt-3">{{$p.t('lehre', 'newlehreinheit')}}</p>
|
||||
</template>
|
||||
|
||||
<template v-if="lv_info">
|
||||
<details-form :data="lv_info"/>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-primary" @click="addNewLehreinheit">{{$p.t('ui', 'speichern')}}</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
configLETabs: {},
|
||||
configLVTabs: {},
|
||||
};
|
||||
},
|
||||
@@ -19,7 +20,7 @@ export default {
|
||||
if (!this.lv || !this.lv.length)
|
||||
return {};
|
||||
|
||||
return this.configLVTabs;
|
||||
return this.configLETabs;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -31,26 +32,42 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$api.call(Setup.getTabs())
|
||||
this.$api.call(Setup.getLETabs())
|
||||
.then(result => {
|
||||
this.configLETabs = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
this.$api.call(Setup.getLVTabs())
|
||||
.then(result => {
|
||||
this.configLVTabs = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
},
|
||||
template: `
|
||||
<div class="stv-details h-100 pb-3 d-flex flex-column">
|
||||
<div v-if="!lv?.length" class="justify-content-center d-flex h-100 align-items-center">
|
||||
Bitte eine Lehreinheit auswählen!
|
||||
</div>
|
||||
<div v-else-if="configLVTabs" class="d-flex flex-column h-100 pb-3">
|
||||
<div v-else-if="configLETabs && configLVTabs" class="d-flex flex-column h-100 pb-3">
|
||||
<fhc-tabs
|
||||
v-if="lv.length === 1"
|
||||
v-if="lv.length === 1 && lv[0]?.lehreinheit_id"
|
||||
ref="tabs"
|
||||
:useprimevue="true"
|
||||
:modelValue="lv[0]"
|
||||
:config="configLETabs"
|
||||
:default="$route.params.tab"
|
||||
style="flex: 1 1 0%; height: 0%"
|
||||
@changed="reload"
|
||||
/>
|
||||
<fhc-tabs
|
||||
v-else-if="lv.length === 1"
|
||||
ref="tabs"
|
||||
:useprimevue="true"
|
||||
:modelValue="lv[0]"
|
||||
:config="configLVTabs"
|
||||
:default="$route.params.tab"
|
||||
style="flex: 1 1 0%; height: 0%"
|
||||
@changed="reload"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import TableLvList from "../../Stv/Studentenverwaltung/Details/Lehrveranstaltungstermine/ListLehrveranstaltungstermine.js";
|
||||
import ApiLVTermine from "../../../api/lehrveranstaltung/lvtermine.js";
|
||||
|
||||
export default {
|
||||
name: "LVTabTermine",
|
||||
components: {
|
||||
TableLvList
|
||||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
endpoint: ApiLVTermine
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="lv-details-course-list h-100 d-flex flex-column">
|
||||
<table-lv-list ref="tbl_course_list" :id="modelValue.lehrveranstaltung_id" :endpoint="endpoint"></table-lv-list>
|
||||
</div>`
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
import NotenZeugnis from "../../Stv/Studentenverwaltung/Details/Noten/Zeugnis.js";
|
||||
import NotenTeacher from "../../Stv/Studentenverwaltung/Details/Noten/Teacher.js";
|
||||
import NotenRepeater from "../../Stv/Studentenverwaltung/Details/Noten/Repeater.js";
|
||||
import ApiLVNoten from "../../../api/lehrveranstaltung/noten.js";
|
||||
import { highlightGesamtnote } from "../../../helpers/DocumentHelper.js";
|
||||
const LOCAL_STORAGE_ID = 'lv_details_noten_2025_12_02_stdsem_all';
|
||||
|
||||
export default {
|
||||
name: "LVTabNoten",
|
||||
components: {
|
||||
NotenZeugnis,
|
||||
NotenTeacher,
|
||||
NotenRepeater
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
config: this.config
|
||||
}
|
||||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
config: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
stdsem: '',
|
||||
endpoint: ApiLVNoten,
|
||||
tabulatorOptionsZeugnis: {
|
||||
visibleColumns: {
|
||||
vorname: true,
|
||||
nachname: true,
|
||||
uid: true,
|
||||
lehrveranstaltung_bezeichnung: false
|
||||
},
|
||||
headerFilter: {
|
||||
vorname: true,
|
||||
nachname: true,
|
||||
uid: true,
|
||||
lehrveranstaltung_bezeichnung: false
|
||||
},
|
||||
persistenceZeugnisID: 'lv-details-noten-zeugnis-2025120401',
|
||||
},
|
||||
tabulatorOptionsTeacher: {
|
||||
visibleColumns: {
|
||||
vorname: true,
|
||||
nachname: true,
|
||||
student_uid: true,
|
||||
lehrveranstaltung_bezeichnung: false
|
||||
},
|
||||
headerFilter: {
|
||||
vorname: true,
|
||||
nachname: true,
|
||||
student_uid: true,
|
||||
lehrveranstaltung_bezeichnung: false
|
||||
},
|
||||
persistenceTeacherID: 'lv-details-noten-teacher-2025120401',
|
||||
},
|
||||
zeugnisLoaded: false,
|
||||
teacherLoaded: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
this.zeugnisLoaded = false;
|
||||
this.teacherLoaded = false;
|
||||
|
||||
this.$refs.zeugnis.$refs.table.reloadTable();
|
||||
this.$refs.teacher.$refs.table.reloadTable();
|
||||
},
|
||||
saveStdsem(event) {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_ID, event.target.value);
|
||||
},
|
||||
onZeugnisLoaded() {
|
||||
this.zeugnisLoaded = true;
|
||||
this.checkHighlight();
|
||||
},
|
||||
onTeacherLoaded() {
|
||||
this.teacherLoaded = true;
|
||||
this.checkHighlight();
|
||||
},
|
||||
checkHighlight()
|
||||
{
|
||||
if (!this.zeugnisLoaded || !this.teacherLoaded)
|
||||
return;
|
||||
|
||||
if (!this.$refs.zeugnis || !this.$refs.teacher)
|
||||
return;
|
||||
|
||||
let zeugnisTable = this.$refs.zeugnis.$refs.table.tabulator;
|
||||
let teacherTable = this.$refs.teacher.$refs.table.tabulator;
|
||||
|
||||
if (!zeugnisTable || !teacherTable)
|
||||
return;
|
||||
|
||||
highlightGesamtnote(zeugnisTable, teacherTable);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const savedPath = window.localStorage.getItem(LOCAL_STORAGE_ID);
|
||||
this.stdsem = savedPath || '';
|
||||
},
|
||||
template: `
|
||||
<div class="stv-details-noten d-flex flex-column overflow-hidden">
|
||||
<div class="mb-3">
|
||||
<select class="form-select" v-model="stdsem" @input="saveStdsem" v-if="config?.semesterSelect ?? true">
|
||||
<option value="">{{ $p.t('ui/current_semester') }}</option>
|
||||
<option value="true">{{ $p.t('ui/all_semester') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<noten-zeugnis
|
||||
ref="zeugnis"
|
||||
:id="modelValue.lehrveranstaltung_id"
|
||||
:all-semester="!!stdsem"
|
||||
:endpoint="endpoint"
|
||||
:optionalTabulatorOptions="tabulatorOptionsZeugnis"
|
||||
@loaded="onZeugnisLoaded"/>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<noten-teacher
|
||||
ref="teacher"
|
||||
:id="modelValue.lehrveranstaltung_id"
|
||||
:all-semester="!!stdsem"
|
||||
:endpoint="endpoint"
|
||||
@copied="reload"
|
||||
:optionalTabulatorOptions="tabulatorOptionsTeacher"
|
||||
@loaded="onTeacherLoaded"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import TableLvList from "../../Stv/Studentenverwaltung/Details/Lehrveranstaltungstermine/ListLehrveranstaltungstermine.js";
|
||||
import ApiLETermine from "../../../api/lehrveranstaltung/letermine.js";
|
||||
|
||||
export default {
|
||||
name: "LETabTermine",
|
||||
components: {
|
||||
TableLvList
|
||||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
endpoint: ApiLETermine
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="le-details-course-list h-100 d-flex flex-column">
|
||||
<table-lv-list ref="tbl_course_list" :id="modelValue.lehreinheit_id" :endpoint="endpoint"></table-lv-list>
|
||||
</div>`
|
||||
};
|
||||
@@ -56,6 +56,7 @@ export default {
|
||||
previewText: null,
|
||||
previewBody: "",
|
||||
replyData: null,
|
||||
isSending: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -113,6 +114,9 @@ export default {
|
||||
data.append('data', JSON.stringify(this.formData));
|
||||
data.append('ids', JSON.stringify(this.id));
|
||||
|
||||
//for disabling sendButton and show Spinner
|
||||
this.isSending = true;
|
||||
|
||||
return this.$refs.formMessage
|
||||
.call(ApiMessages.sendMessage(this.typeId, data))
|
||||
.then(response => {
|
||||
@@ -121,6 +125,7 @@ export default {
|
||||
this.resetForm();
|
||||
}).catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.isSending = false;
|
||||
|
||||
//just emit if no multitasking
|
||||
if(this.id.length == 1){
|
||||
@@ -288,6 +293,9 @@ export default {
|
||||
dialog-class=" modal-dialog-scrollable modal-xl modal-msg"
|
||||
header-class="flex-wrap pb-0"
|
||||
body-class="px-3 py-2"
|
||||
:backdrop="isSending ? 'static' : true"
|
||||
:keyboard="!isSending"
|
||||
:noCloseBtn="isSending"
|
||||
@hidden.bs.modal="resetForm"
|
||||
>
|
||||
|
||||
@@ -476,7 +484,12 @@ export default {
|
||||
|
||||
<button class="btn btn-secondary" @click="resetForm">{{$p.t('ui', 'reset')}}</button>
|
||||
|
||||
<button v-if="statusNew" type="button" class="btn btn-primary" @click="sendMessage()">{{$p.t('ui', 'nachrichtSenden')}}</button>
|
||||
<button v-if="statusNew" type="button" class="btn btn-primary" :disabled="isSending" @click="sendMessage()">
|
||||
<span
|
||||
v-if="isSending"
|
||||
class="spinner-border spinner-border-sm me-2"
|
||||
></span>
|
||||
{{$p.t('ui', 'nachrichtSenden')}}</button>
|
||||
<button v-else type="button" class="btn btn-primary" @click="replyMessage(formData.message_id)">{{$p.t('global', 'reply')}}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -55,6 +55,7 @@ export default {
|
||||
previewBody: "",
|
||||
replyData: null,
|
||||
messageSent: false,
|
||||
isSending: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -104,16 +105,21 @@ export default {
|
||||
data.append('data', JSON.stringify(this.formData));
|
||||
data.append('ids', JSON.stringify(this.id));
|
||||
|
||||
//for disabling sendButton and show Spinner
|
||||
this.isSending = true;
|
||||
|
||||
return this.$api
|
||||
.call(ApiMessages.sendMessage(this.typeId, data))
|
||||
.then(response => {
|
||||
if(this.openMode == "inSamePage")
|
||||
if(this.openMode == "inSamePage") {
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSent'));
|
||||
}
|
||||
this.hideTemplate();
|
||||
this.resetForm();
|
||||
this.messageSent = true;
|
||||
}).catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.isSending = false;
|
||||
if(this.openMode == "inSamePage" && this.id.length == 1 ){
|
||||
this.$emit('reloadTable');
|
||||
}
|
||||
@@ -413,7 +419,12 @@ export default {
|
||||
|
||||
<button class="btn btn-secondary" @click="resetForm">{{$p.t('ui', 'reset')}}</button>
|
||||
|
||||
<button v-if="statusNew" type="button" class="btn btn-primary" @click="sendMessage()">{{$p.t('ui', 'nachrichtSenden')}}</button>
|
||||
<button v-if="statusNew" type="button" class="btn btn-primary" :disabled="isSending" @click="sendMessage()">
|
||||
<span
|
||||
v-if="isSending"
|
||||
class="spinner-border spinner-border-sm me-2"
|
||||
></span>
|
||||
{{$p.t('ui', 'nachrichtSenden')}}</button>
|
||||
<button v-else type="button" class="btn btn-primary" @click="replyMessage(formData.message_id)">{{$p.t('global', 'reply')}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import CoreSearchbar from "../searchbar/searchbar.js";
|
||||
import NavLanguage from "../navigation/Language.js";
|
||||
import VerticalSplit from "../verticalsplit/verticalsplit.js";
|
||||
import HorizontalSplit from "../horizontalsplit/horizontalsplit.js";
|
||||
import AppMenu from "../AppMenu.js";
|
||||
import AppConfig from "../AppConfig.js";
|
||||
import StvVerband from "./Studentenverwaltung/Verband.js";
|
||||
@@ -26,8 +27,8 @@ import StvDetails from "./Studentenverwaltung/Details.js";
|
||||
import StvStudiensemester from "./Studentenverwaltung/Studiensemester.js";
|
||||
|
||||
import ApiSearchbar from "../../api/factory/searchbar.js";
|
||||
import ApiTreemenu from "../../api/factory/treemenu.js";
|
||||
import ApiStv from "../../api/factory/stv.js";
|
||||
import ApiStvVerband from '../../api/factory/stv/verband.js';
|
||||
import ApiStvConfig from '../../api/factory/stv/config.js';
|
||||
|
||||
|
||||
@@ -37,6 +38,7 @@ export default {
|
||||
CoreSearchbar,
|
||||
NavLanguage,
|
||||
VerticalSplit,
|
||||
HorizontalSplit,
|
||||
AppMenu,
|
||||
AppConfig,
|
||||
StvVerband,
|
||||
@@ -53,11 +55,7 @@ export default {
|
||||
avatarUrl: String,
|
||||
logoutUrl: String,
|
||||
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
|
||||
authUid: String
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
@@ -91,7 +89,9 @@ export default {
|
||||
hasZGVMasterPermission: this.permissions['student/editMakkZgv'],
|
||||
hasZGVDoctorPermission: this.permissions['student/editDokZgv'],
|
||||
hasBismeldenPermission: this.permissions['student/editBismelden'],
|
||||
|
||||
authUid: this.authUid,
|
||||
language: Vue.computed(() => this.$p.user_language),
|
||||
isMobile: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -140,7 +140,6 @@ export default {
|
||||
mergeResults: 'person'
|
||||
},
|
||||
studiengangKz: undefined,
|
||||
studiengangKuerzel: '',
|
||||
studiensemesterKurzbz: this.defaultSemester,
|
||||
selected_semester: undefined,
|
||||
selected_orgform: undefined,
|
||||
@@ -149,7 +148,6 @@ export default {
|
||||
sprachen: [],
|
||||
geschlechter: []
|
||||
},
|
||||
verbandEndpoint: ApiStvVerband,
|
||||
filter: []
|
||||
}
|
||||
},
|
||||
@@ -192,34 +190,111 @@ export default {
|
||||
}
|
||||
|
||||
return extraItems;
|
||||
},
|
||||
appMenuLvPlanungItems() {
|
||||
const extraItems = [];
|
||||
|
||||
if (this.studiengangKz !== undefined && this.selected_semester !== undefined) {
|
||||
const studiengang_kz = String(this.studiengangKz);
|
||||
const semester = String(this.selected_semester);
|
||||
const orgform = this.selected_orgform || '';
|
||||
|
||||
extraItems.push({
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'content/statistik/lvplanung.xls.php?'
|
||||
+ '&studiengang_kz=' + studiengang_kz
|
||||
+ '&semester=' + semester
|
||||
+ '&studiensemester_kurzbz=' + this.studiensemesterKurzbz
|
||||
+ '&orgform_kurzbz=' + orgform,
|
||||
description: 'stv/lvplanung_xls'
|
||||
});
|
||||
extraItems.push({
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'content/statistik/lvplanung.php?'
|
||||
+ '&studiengang_kz=' + studiengang_kz
|
||||
+ '&semester=' + semester,
|
||||
description: 'stv/lvplanung_html'
|
||||
});
|
||||
}
|
||||
|
||||
return extraItems;
|
||||
},
|
||||
appMenuBisItems() {
|
||||
const extraItems = [];
|
||||
|
||||
if (this.studiengangKz !== undefined) {
|
||||
const studiengang_kz = String(this.studiengangKz);
|
||||
|
||||
extraItems.push({
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'vilesci/bis/studentenmeldung.php?'
|
||||
+ 'stg_kz=' + studiengang_kz,
|
||||
description: 'stv/bis_stud_meldung'
|
||||
});
|
||||
extraItems.push({
|
||||
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'system/checkStudenten.php?'
|
||||
+ 'stg_kz=' + studiengang_kz,
|
||||
description: 'stv/bis_stud_plausicheck'
|
||||
});
|
||||
}
|
||||
|
||||
return extraItems;
|
||||
},
|
||||
linkRt(){
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + '/vilesci/stammdaten/reihungstestverwaltung.php'
|
||||
},
|
||||
selected_uid(){
|
||||
return this.selected?.[this.selected.length - 1]?.uid ?? null;
|
||||
},
|
||||
linkGradeList(){
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'index.ci.php/person/gradelist/index/' + this.selected_uid
|
||||
},
|
||||
listEndpoint() {
|
||||
if (['index', 'stdsem'].includes(this.$route.name))
|
||||
return null;
|
||||
|
||||
if (this.$route.params.query) {
|
||||
return ApiStv.students.search({
|
||||
searchstr: this.$route.params.query,
|
||||
types: this.$route.params.types?.split('+') || []
|
||||
}, this.$route.params.stdsem);
|
||||
}
|
||||
|
||||
let url = 'stdsem/' + this.$route.params.stdsem + '/';
|
||||
|
||||
if (this.$route.params.treemenu) {
|
||||
url += this.$route.params.treemenu.join('/');
|
||||
return ApiTreemenu.data('stv', url);
|
||||
}
|
||||
|
||||
const paramName = Object.keys(this.$route.params).find(k => k != 'stdsem');
|
||||
if (paramName) {
|
||||
url += this.$route.name + '/' + this.$route.params[paramName];
|
||||
return ApiTreemenu.data('stv', url);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
studiengangKuerzel() {
|
||||
if (!this.lists?.stgs)
|
||||
return '';
|
||||
|
||||
const stg = this.lists.stgs
|
||||
.find(stg => stg.studiengang_kz == this.studiengangKz);
|
||||
|
||||
if (stg)
|
||||
return (stg.typ + stg.kurzbz).toUpperCase();
|
||||
|
||||
return '';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'url_studiensemester_kurzbz': function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.studiensemesterKurzbz = newVal;
|
||||
if(this.$route.name === 'search')
|
||||
{
|
||||
this.handleSearchUrl();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.$refs.stvList.updateUrl();
|
||||
this.$refs.details.reload();
|
||||
}
|
||||
listEndpoint(n, o) {
|
||||
if (n != o) {
|
||||
this.$refs.stvList.updateUrl(n || []);
|
||||
}
|
||||
},
|
||||
'url_studiengang': function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.checkUrlStudiengang();
|
||||
}
|
||||
},
|
||||
'url_mode': function () {
|
||||
this.handlePersonUrl();
|
||||
},
|
||||
url_prestudent_id() {
|
||||
this.handlePersonUrl();
|
||||
},
|
||||
'appconfig.font_size'() {
|
||||
// add to html class
|
||||
const classList = Object.keys(this.$refs.config.setup.font_size.options);
|
||||
@@ -235,6 +310,15 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sidebarCollapsed(newVal) {
|
||||
if(newVal) this.$refs.hSplit.collapseLeft()
|
||||
else this.$refs.hSplit.showBoth()
|
||||
},
|
||||
studiengangKz(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.$refs.stvList.clearSelection();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -272,177 +356,99 @@ export default {
|
||||
);
|
||||
},
|
||||
onSelectVerband({ link, studiengang_kz, semester, orgform_kurzbz }) {
|
||||
let urlpath = String(link);
|
||||
if (!urlpath.match(/\/prestudent/))
|
||||
{
|
||||
urlpath = 'CURRENT_SEMESTER' + '/' + urlpath;
|
||||
}
|
||||
this.$refs.stvList.updateUrl(ApiStv.students.verband(urlpath));
|
||||
|
||||
this.studiengangKz = studiengang_kz;
|
||||
this.selected_semester = semester;
|
||||
this.selected_orgform = orgform_kurzbz;
|
||||
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;
|
||||
|
||||
this.$router.push({
|
||||
name: 'treemenu',
|
||||
params: {
|
||||
studiensemester_kurzbz: v
|
||||
treemenu: link.split('/')
|
||||
}
|
||||
});
|
||||
},
|
||||
studiensemesterChanged(stdsem) {
|
||||
if (!stdsem) {
|
||||
// no valid studiensemester in url
|
||||
this.$router.replace({
|
||||
params: {
|
||||
stdsem: this.defaultSemester.toLowerCase()
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (stdsem.toLowerCase() != this.studiensemesterKurzbz?.toLowerCase()) {
|
||||
this.$router.push({
|
||||
params: {
|
||||
stdsem: stdsem.toLowerCase()
|
||||
}
|
||||
});
|
||||
}
|
||||
this.studiensemesterKurzbz = stdsem;
|
||||
},
|
||||
reloadList() {
|
||||
this.$refs.stvList.reload();
|
||||
},
|
||||
searchfunction(params, config) {
|
||||
return this.$api.call(ApiSearchbar.searchStv(params), config);
|
||||
},
|
||||
handlePersonUrl() {
|
||||
if (this.$route.params.id) {
|
||||
this.$refs.stvList.updateUrl(
|
||||
ApiStv.students.uid(this.$route.params.id, 'CURRENT_SEMESTER'),
|
||||
true
|
||||
);
|
||||
} else if (this.$route.params.prestudent_id) {
|
||||
this.$refs.stvList.updateUrl(
|
||||
ApiStv.students.prestudent(this.$route.params.prestudent_id, 'CURRENT_SEMESTER'),
|
||||
true
|
||||
);
|
||||
} else if (this.$route.params.person_id) {
|
||||
this.$refs.stvList.updateUrl(
|
||||
ApiStv.students.person(this.$route.params.person_id, 'CURRENT_SEMESTER'),
|
||||
true
|
||||
);
|
||||
} else if (this.$route.params.searchstr) {
|
||||
this.handleSearchUrl();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.clearTabulator();
|
||||
}
|
||||
},
|
||||
handleSearchUrl() {
|
||||
const searchsettings = {
|
||||
searchstr: this.$route.params.searchstr,
|
||||
types: this.$route.params.types?.split('+') || []
|
||||
};
|
||||
|
||||
// init into student list
|
||||
this.$refs.stvList.updateUrl(
|
||||
ApiStv.students.search(searchsettings, this.studiensemesterKurzbz)
|
||||
);
|
||||
|
||||
// init into searchbar
|
||||
this.$refs.searchbar.searchsettings.searchstr = searchsettings.searchstr;
|
||||
this.$refs.searchbar.searchsettings.types = searchsettings.types;
|
||||
this.$nextTick(this.blurSearchbar);
|
||||
},
|
||||
clearTabulator() {
|
||||
if(['index', 'studiensemester'].includes(this.$route.name))
|
||||
{
|
||||
if(this.$refs?.stvList?.$refs?.table?.tabulator)
|
||||
{
|
||||
this.$refs.stvList.$refs.table.tabulator.setData([]);
|
||||
}
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.studiengangKz = undefined;
|
||||
this.studiengangKuerzel = '';
|
||||
this.clearTabulator();
|
||||
}
|
||||
},
|
||||
onSearch(e) {
|
||||
this.deleteCustomFilter();
|
||||
const searchsettings = { ...this.$refs.searchbar.searchsettings };
|
||||
|
||||
if (searchsettings.searchstr.length >= 2) {
|
||||
this.blurSearchbar();
|
||||
this.deleteCustomFilter();
|
||||
|
||||
if (!searchsettings.types.length || searchsettings.types.length == this.$refs.searchbar.types.length) {
|
||||
this.$router.push({
|
||||
name: 'search',
|
||||
params: {
|
||||
studiensemester_kurzbz: this.studiensemesterKurzbz,
|
||||
searchstr: searchsettings.searchstr
|
||||
query: searchsettings.searchstr
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: 'search_w_types',
|
||||
name: 'searchtypes',
|
||||
params: {
|
||||
studiensemester_kurzbz: this.studiensemesterKurzbz,
|
||||
searchstr: searchsettings.searchstr,
|
||||
query: searchsettings.searchstr,
|
||||
types: searchsettings.types.join('+')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
blurSearchbar() {
|
||||
this.$refs.searchbar.$refs.input.blur();
|
||||
this.$refs.searchbar.abort();
|
||||
this.$refs.searchbar.hideresult();
|
||||
},
|
||||
handleCustomFilter(filter){
|
||||
this.filter = filter;
|
||||
},
|
||||
deleteCustomFilter(){
|
||||
this.$refs.stvList.resetFilter();
|
||||
},
|
||||
showAlertNoSelectedStudent(){
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseStudent'));
|
||||
},
|
||||
showAlertMultipleStudents() {
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseOnlyOneStudent'));
|
||||
},
|
||||
showAlertNoGroupChosen(){
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseGroupSem'));
|
||||
},
|
||||
showAlertNoDegreeProgramChosen(){
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseDegreeProgram'));
|
||||
},
|
||||
showMenu(refName) {
|
||||
bootstrap.Dropdown.getOrCreateInstance(this.$refs[refName]).show();
|
||||
},
|
||||
hideMenu(refName) {
|
||||
bootstrap.Dropdown.getOrCreateInstance(this.$refs[refName]).hide();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.url_studiensemester_kurzbz) {
|
||||
if (!this.$route.params.stdsem) {
|
||||
this.$router.replace({
|
||||
name: 'studiensemester',
|
||||
name: 'stdsem',
|
||||
params: {
|
||||
studiensemester_kurzbz: this.defaultSemester
|
||||
stdsem: this.defaultSemester.toLowerCase()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.studiensemesterKurzbz = this.url_studiensemester_kurzbz;
|
||||
}
|
||||
|
||||
this.$api
|
||||
@@ -478,7 +484,6 @@ 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);
|
||||
|
||||
@@ -505,7 +510,8 @@ export default {
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
mounted() {
|
||||
this.handlePersonUrl();
|
||||
if (this.listEndpoint)
|
||||
this.$refs.stvList.updateUrl(this.listEndpoint);
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="stv" :class="{ 'sidebar-collapsed': sidebarCollapsed }">
|
||||
@@ -606,19 +612,30 @@ export default {
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<app-menu app-identifier="stv">
|
||||
<li class="dropend">
|
||||
<li :class="{ dropend: appMenuExtraItems.length }"
|
||||
@mouseenter="appMenuExtraItems.length && showMenu('gradeReportToggle')"
|
||||
@mouseleave="appMenuExtraItems.length && hideMenu('gradeReportToggle')"
|
||||
>
|
||||
<a
|
||||
ref="gradeReportToggle"
|
||||
v-if="appMenuExtraItems.length"
|
||||
class="dropdown-toggle"
|
||||
href="#"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
:class="{ disabled: !appMenuExtraItems.length }"
|
||||
data-bs-popper-config='{"strategy":"fixed"}'
|
||||
>
|
||||
{{ $p.t('stv/grade_report') }}
|
||||
</a>
|
||||
<ul class="dropdown-menu p-0">
|
||||
<a
|
||||
v-else
|
||||
href="#"
|
||||
@click.prevent="showAlertNoGroupChosen"
|
||||
>
|
||||
{{ $p.t('stv/grade_report') }}
|
||||
</a>
|
||||
<ul v-if="appMenuExtraItems.length" class="dropdown-menu p-0">
|
||||
<li
|
||||
v-for="(item, key) in appMenuExtraItems"
|
||||
:key="key"
|
||||
@@ -629,26 +646,120 @@ export default {
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li :class="{ dropend: appMenuLvPlanungItems.length }"
|
||||
@mouseenter="appMenuLvPlanungItems.length && showMenu('lvPlanungToggle')"
|
||||
@mouseleave="appMenuLvPlanungItems.length && hideMenu('lvPlanungToggle')"
|
||||
>
|
||||
<a
|
||||
ref="lvPlanungToggle"
|
||||
v-if="appMenuLvPlanungItems.length"
|
||||
class="dropdown-toggle"
|
||||
href="#"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-bs-popper-config='{"strategy":"fixed"}'
|
||||
>
|
||||
{{ $p.t('stv/lvplanung') }}
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
href="#"
|
||||
@click.prevent="showAlertNoGroupChosen"
|
||||
>
|
||||
{{ $p.t('stv/lvplanung') }}
|
||||
</a>
|
||||
<ul v-if="appMenuLvPlanungItems.length" class="dropdown-menu p-0">
|
||||
<li
|
||||
v-for="(item, key) in appMenuLvPlanungItems"
|
||||
:key="key"
|
||||
>
|
||||
<a class="dropdown-item" :href="item.link" target="_blank">
|
||||
{{ $p.t(item.description) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a :href="linkRt" target="_blank">
|
||||
{{ $p.t('stv/RTVerwaltung') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a v-if="selected.length === 1" :href="linkGradeList" target="_blank">
|
||||
{{ $p.t('stv/studienverlauf') }}
|
||||
</a>
|
||||
<a v-else-if="selected.length === 0" href="#" @click.prevent="showAlertNoSelectedStudent">
|
||||
{{ $p.t('stv/studienverlauf') }}
|
||||
</a>
|
||||
<a v-else href="#" @click.prevent="showAlertMultipleStudents">
|
||||
{{ $p.t('stv/studienverlauf') }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li :class="{ dropend: appMenuBisItems.length }"
|
||||
@mouseenter="appMenuBisItems.length && showMenu('bisToggle')"
|
||||
@mouseleave="appMenuBisItems.length && hideMenu('bisToggle')"
|
||||
>
|
||||
<a
|
||||
ref="bisToggle"
|
||||
v-if="appMenuBisItems.length"
|
||||
class="dropdown-toggle"
|
||||
href="#"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
data-bs-popper-config='{"strategy":"fixed"}'
|
||||
>
|
||||
{{ $p.t('stv/bis_stud') }}
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
href="#"
|
||||
@click.prevent="showAlertNoDegreeProgramChosen"
|
||||
>
|
||||
{{ $p.t('stv/bis_stud') }}
|
||||
</a>
|
||||
<ul v-if="appMenuBisItems.length" class="dropdown-menu p-0">
|
||||
<li
|
||||
v-for="(item, key) in appMenuBisItems"
|
||||
:key="key"
|
||||
>
|
||||
<a class="dropdown-item" :href="item.link" target="_blank">
|
||||
{{ $p.t(item.description) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</app-menu>
|
||||
</div>
|
||||
</aside>
|
||||
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
|
||||
<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 :preselectedKey="studiengangKz ? '' + studiengangKz : null" :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">
|
||||
<vertical-split>
|
||||
<template #top>
|
||||
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filterActive="handleCustomFilter"></stv-list>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
|
||||
</template>
|
||||
</vertical-split>
|
||||
</main>
|
||||
|
||||
<horizontal-split ref="hSplit" :defaultRatio="[15, 85]">
|
||||
<template #left>
|
||||
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 w-100">
|
||||
<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 :preselected-key="$route.params.treemenu" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
|
||||
<stv-studiensemester :studiensemester-kurzbz="$route.params.stdsem || defaultSemester" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
|
||||
</nav>
|
||||
</template>
|
||||
<template #right>
|
||||
<main>
|
||||
<vertical-split :defaultRatio="[50, 50]">
|
||||
<template #top>
|
||||
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filter-active="handleCustomFilter"></stv-list>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
|
||||
</template>
|
||||
</vertical-split>
|
||||
</main>
|
||||
</template>
|
||||
</horizontal-split>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
|
||||
|
||||
@@ -2,12 +2,18 @@ import FhcTabs from "../../Tabs.js";
|
||||
import FhcHeader from "../../DetailHeader/DetailHeader.js";
|
||||
|
||||
import ApiStvApp from '../../../api/factory/stv/app.js';
|
||||
import ApiStudent from '../../../api/factory/stv/students.js';
|
||||
|
||||
// TODO(chris): alt & title
|
||||
// TODO(chris): phrasen
|
||||
|
||||
export default {
|
||||
name: "DetailsPrestudent",
|
||||
inject: {
|
||||
currentSemester: {
|
||||
from: 'currentSemester',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
FhcTabs,
|
||||
FhcHeader
|
||||
@@ -15,7 +21,9 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
configStudent: {},
|
||||
configStudents: {}
|
||||
configStudents: {},
|
||||
activeTab: null,
|
||||
localStudent: null
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -40,6 +48,9 @@ export default {
|
||||
}
|
||||
return Object.fromEntries(Object.entries(this.configStudents).filter(([ , value ]) => !value.showOnlyWithUid && !value.showOnlyWithUid));
|
||||
},
|
||||
isLoading() {
|
||||
return this.students === null; //null-> loading, [] -> empty, [...] -> data, necessary for skeleton in child
|
||||
},
|
||||
tile_PersId(){
|
||||
let tile = this.students[0].person_id != null ? this.students[0].person_id : '-';
|
||||
return tile;
|
||||
@@ -52,11 +63,34 @@ export default {
|
||||
let tile = this.students[0].matrikelnr != null ? this.students[0].matrikelnr : '-';
|
||||
return tile;
|
||||
},
|
||||
tile_PrestdId(){
|
||||
let tile = this.students[0].prestudent_id != null ? this.students[0].prestudent_id : '-';
|
||||
return tile;
|
||||
},
|
||||
tile_UID(){
|
||||
let tile = this.students[0].uid != null ? this.students[0].uid : '-';
|
||||
return tile;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$p.user_language.value'(n, o) {
|
||||
if (n !== o && o !== undefined)
|
||||
this.loadConfig();
|
||||
},
|
||||
currentSemester(newVal) {
|
||||
if (
|
||||
Array.isArray(this.students) &&
|
||||
this.students.length === 1 &&
|
||||
newVal !== this.students[0].query_studiensemester_kurzbz
|
||||
) {
|
||||
this.reloadDataStudent();
|
||||
}
|
||||
else {
|
||||
this.localStudent = null;
|
||||
}
|
||||
},
|
||||
students() {
|
||||
this.localStudent = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -74,10 +108,28 @@ export default {
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
handleTabChanged(key) {
|
||||
this.activeTab = key
|
||||
this.reload()
|
||||
},
|
||||
reload() {
|
||||
if (this.$refs.tabs?.$refs?.current?.reload)
|
||||
this.$refs.tabs.$refs.current.reload();
|
||||
},
|
||||
reloadDataStudent(){
|
||||
this.localStudent = null;
|
||||
const studentArr = this.students;
|
||||
|
||||
if (!studentArr || !studentArr.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$api
|
||||
.call(ApiStudent.uid(studentArr[0].uid, this.currentSemester))
|
||||
.then(result => {
|
||||
this.localStudent = result.data;
|
||||
});
|
||||
},
|
||||
reloadList() {
|
||||
this.$emit('reload');
|
||||
},
|
||||
@@ -92,31 +144,35 @@ export default {
|
||||
</div>
|
||||
<div v-else-if="configStudent && configStudents" class="d-flex flex-column h-100">
|
||||
<fhc-header
|
||||
:headerData="students"
|
||||
:headerData="localStudent || students"
|
||||
:currentSemester="currentSemester"
|
||||
typeHeader="student"
|
||||
@reload="reloadList"
|
||||
fotoEditable
|
||||
:isLoading="isLoading"
|
||||
>
|
||||
<template #uid>{{students[0].uid}}</template>
|
||||
<template #titleAlphaTile>PersID</template>
|
||||
<template #valueAlphaTile>{{tile_PersId}}</template>
|
||||
<template #titleBetaTile>MatrNr</template>
|
||||
<template #valueBetaTile>{{tile_MatrNr}}</template>
|
||||
<template #titleGammaTile>PersKz</template>
|
||||
<template #valueGammaTile>{{tile_PersKz}}</template>
|
||||
<template #uid>{{tile_UID}}</template>
|
||||
<template #titleAlphaTile>PrestdID</template>
|
||||
<template #valueAlphaTile>{{tile_PrestdId}}</template>
|
||||
<template #titleBetaTile>PersID</template>
|
||||
<template #valueBetaTile>{{tile_PersId}}</template>
|
||||
<template #titleGammaTile>MatrNr</template>
|
||||
<template #valueGammaTile>{{tile_MatrNr}}</template>
|
||||
<template #titleDeltaTile>PersKz</template>
|
||||
<template #valueDeltaTile>{{tile_PersKz}}</template>
|
||||
</fhc-header>
|
||||
<fhc-tabs
|
||||
v-if="students.length == 1"
|
||||
ref="tabs"
|
||||
ref="tabs"
|
||||
:useprimevue="true"
|
||||
:modelValue="students[0]"
|
||||
:modelValue="(Array.isArray(localStudent) && localStudent[0]) || students[0]"
|
||||
:config="config"
|
||||
:default="$route.params.tab"
|
||||
:default="activeTab ?? $route.params.tab"
|
||||
style="flex: 1 1 0%; height: 0%"
|
||||
@changed="reload"
|
||||
@changed="handleTabChanged"
|
||||
>
|
||||
</fhc-tabs>
|
||||
<fhc-tabs v-else ref="tabs" :useprimevue="true" :modelValue="students" :config="config" :default="$route.params.tab" style="flex: 1 1 0%; height: 0%" @changed="reload"></fhc-tabs>
|
||||
<fhc-tabs v-else ref="tabs" :useprimevue="true" :modelValue="students" :config="config" :default="activeTab ?? $route.params.tab" style="flex: 1 1 0%; height: 0%" @changed="handleTabChanged"></fhc-tabs>
|
||||
</div>
|
||||
<div v-else>
|
||||
Loading...
|
||||
|
||||
+195
-40
@@ -78,7 +78,7 @@ export default {
|
||||
layout: 'fitDataStretchFrozen',
|
||||
layoutColumnsOnNewData: false,
|
||||
height: 'auto',
|
||||
minHeight: '200',
|
||||
minHeight: '200'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -238,6 +238,16 @@ export default {
|
||||
}
|
||||
return this.student.map(e => e.uid);
|
||||
},
|
||||
studentNames() {
|
||||
if (this.student.uid)
|
||||
{
|
||||
return [this.student.vorname + ' ' + this.student.nachname];
|
||||
}
|
||||
|
||||
const array = this.student.map(e => ' ' + e.vorname + ' ' + e.nachname + '(' + e.uid +')');
|
||||
|
||||
return array.toString();
|
||||
},
|
||||
studentKzs(){
|
||||
if (this.student.uid)
|
||||
{
|
||||
@@ -287,10 +297,51 @@ export default {
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
actionNewAbschlusspruefung() {
|
||||
this.resetForm();
|
||||
this.statusNew = true;
|
||||
this.$refs.finalexamModal.show();
|
||||
this.setDefaultFormData();
|
||||
this.statusNew = true;
|
||||
|
||||
//prepare local Storage
|
||||
let STORAGE_KEY = 'finalExamDefaultData';
|
||||
const id = '20260625_01';
|
||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
|
||||
|
||||
if (stored[id]) {
|
||||
const data = stored[id];
|
||||
|
||||
this.formData.pruefungstyp_kurzbz = data.pruefungstyp_kurzbz;
|
||||
this.formData.datum = data.datum;
|
||||
this.formData.sponsion = data.sponsion;
|
||||
this.formData.akadgrad_id = data.akadgrad_id;
|
||||
this.formData.pruefungsantritt_kurzbz = data.pruefungsantritt_kurzbz;
|
||||
|
||||
if (data.vorsitz_uid) {
|
||||
this.selectedVorsitz = {
|
||||
mitarbeiter_uid: data.vorsitz_uid,
|
||||
person_id: data.vorsitz_person_id,
|
||||
label: data.vorsitz_label
|
||||
};
|
||||
}
|
||||
|
||||
if (data.pruefer1_person_id) {
|
||||
this.selectedPruefer1 = {
|
||||
person_id: data.pruefer1_person_id,
|
||||
label: data.pruefer1_label
|
||||
};
|
||||
}
|
||||
if (data.pruefer2_person_id) {
|
||||
this.selectedPruefer2 = {
|
||||
person_id: data.pruefer2_person_id,
|
||||
label: data.pruefer2_label
|
||||
};
|
||||
}
|
||||
if (data.pruefer3_person_id) {
|
||||
this.selectedPruefer3 = {
|
||||
person_id: data.pruefer3_person_id,
|
||||
label: data.pruefer3_label
|
||||
};
|
||||
}
|
||||
}
|
||||
this.$refs.finalexamModal.show();
|
||||
},
|
||||
actionEditAbschlusspruefung(abschlusspruefung_id) {
|
||||
this.resetForm();
|
||||
@@ -305,20 +356,23 @@ export default {
|
||||
};
|
||||
if (data.p1_person_id) {
|
||||
this.selectedPruefer1 = {
|
||||
label: this.getPersonLabel(data.p1_titelpre, data.p1_nachname, data.p1_vorname, data.p1_titelpost),
|
||||
person_id: data.p1_person_id
|
||||
label: this.getPersonLabel(data.p1_titelpre, data.p1_nachname, data.p1_vorname, data.p1_titelpost, data.p1_uid),
|
||||
person_id: data.p1_person_id,
|
||||
mitarbeiter_uid: data.p1_uid
|
||||
};
|
||||
}
|
||||
if (data.p2_person_id) {
|
||||
this.selectedPruefer2 = {
|
||||
label: this.getPersonLabel(data.p2_titelpre, data.p2_nachname, data.p2_vorname, data.p2_titelpost),
|
||||
person_id: data.p2_person_id
|
||||
label: this.getPersonLabel(data.p2_titelpre, data.p2_nachname, data.p2_vorname, data.p2_titelpost, data.p2_uid),
|
||||
person_id: data.p2_person_id,
|
||||
mitarbeiter_uid: data.p2_uid
|
||||
}
|
||||
};
|
||||
if (data.p3_person_id) {
|
||||
this.selectedPruefer3= {
|
||||
label: this.getPersonLabel(data.p3_titelpre, data.p3_nachname, data.p3_vorname, data.p3_titelpost),
|
||||
person_id: data.p3_person_id
|
||||
label: this.getPersonLabel(data.p3_titelpre, data.p3_nachname, data.p3_vorname, data.p3_titelpost, data.p3_uid),
|
||||
person_id: data.p3_person_id,
|
||||
mitarbeiter_uid: data.p3_uid
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -337,6 +391,30 @@ export default {
|
||||
.then(this.deleteAbschlusspruefung)
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
saveOrUpdateLocalStorage(){
|
||||
let STORAGE_KEY = 'finalExamDefaultData';
|
||||
|
||||
const id = '20260625_01';
|
||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
|
||||
|
||||
stored[id] = {
|
||||
pruefungstyp_kurzbz: this.formData.pruefungstyp_kurzbz,
|
||||
pruefungsantritt_kurzbz: this.formData.pruefungsantritt_kurzbz,
|
||||
vorsitz_uid: this.selectedVorsitz?.mitarbeiter_uid || null,
|
||||
vorsitz_person_id: this.selectedVorsitz?.person_id || null,
|
||||
vorsitz_label: this.selectedVorsitz?.label || null,
|
||||
pruefer1_person_id: this.selectedPruefer1?.person_id || null,
|
||||
pruefer1_label: this.selectedPruefer1?.label || null,
|
||||
pruefer2_person_id: this.selectedPruefer2?.person_id || null,
|
||||
pruefer2_label: this.selectedPruefer2?.label || null,
|
||||
pruefer3_person_id: this.selectedPruefer3?.person_id || null,
|
||||
pruefer3_label: this.selectedPruefer3?.label || null,
|
||||
akadgrad_id: this.formData.akadgrad_id,
|
||||
datum: this.formData.datum,
|
||||
sponsion: this.formData.sponsion
|
||||
};
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
||||
},
|
||||
addNewAbschlusspruefung() {
|
||||
const dataToSend = {
|
||||
uid: this.student.uid,
|
||||
@@ -347,6 +425,8 @@ export default {
|
||||
.call(ApiStvAbschlusspruefung.addNewAbschlusspruefung(dataToSend))
|
||||
.then(response => {
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
|
||||
//save formData fields in LocalStorage
|
||||
this.saveOrUpdateLocalStorage();
|
||||
this.hideModal('finalexamModal');
|
||||
this.resetForm();
|
||||
})
|
||||
@@ -355,6 +435,43 @@ export default {
|
||||
this.reload();
|
||||
});
|
||||
},
|
||||
async addNewAbschlusspruefungMulti(){
|
||||
try {
|
||||
const arraySuccessfullySent = [];
|
||||
const arrayError = [];
|
||||
|
||||
for (const student of this.studentUids) {
|
||||
try {
|
||||
await this.$refs.formFinalExam.call(
|
||||
ApiStvAbschlusspruefung.addNewAbschlusspruefung({
|
||||
uid: student,
|
||||
formData: this.formData
|
||||
})
|
||||
);
|
||||
arraySuccessfullySent.push(student);
|
||||
} catch (error) {
|
||||
arrayError.push(student);
|
||||
}
|
||||
}
|
||||
if (arraySuccessfullySent.length) {
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
|
||||
//TODO(Manu) check if really needed in case of success
|
||||
//this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave') + ': Uids: ' + arraySuccessfullySent.join(", "));
|
||||
}
|
||||
|
||||
if (arrayError.length) {
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'errorSavingData') + ': Uids: ' + arrayError.join(", "));
|
||||
}
|
||||
|
||||
if (arraySuccessfullySent.length) {
|
||||
this.saveOrUpdateLocalStorage();
|
||||
this.hideModal("finalexamModal");
|
||||
this.resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
},
|
||||
hideModal(modalRef){
|
||||
this.$refs[modalRef].hide();
|
||||
},
|
||||
@@ -376,6 +493,9 @@ export default {
|
||||
id: abschlusspruefung_id,
|
||||
formData: this.formData
|
||||
};
|
||||
//uncomment if also save data in local storage for update
|
||||
//this.saveOrUpdateLocalStorage();
|
||||
|
||||
return this.$refs.formFinalExam
|
||||
.call(ApiStvAbschlusspruefung.updateAbschlusspruefung(dataToSend))
|
||||
.then(response => {
|
||||
@@ -417,7 +537,6 @@ export default {
|
||||
this.selectedPruefer1 = null;
|
||||
this.selectedPruefer2 = null;
|
||||
this.selectedPruefer3 = null;
|
||||
|
||||
},
|
||||
setDefaultFormData() {
|
||||
|
||||
@@ -434,7 +553,14 @@ export default {
|
||||
}
|
||||
|
||||
if (!this.formData.akadgrad_id && this.arrAkadGrad.length > 0) {
|
||||
this.formData.akadgrad_id = this.arrAkadGrad[0].akadgrad_id;
|
||||
const akadGradByGeschlecht = this.arrAkadGrad.find((item) => {
|
||||
return item.geschlecht === this.student.geschlecht;
|
||||
});
|
||||
|
||||
if(akadGradByGeschlecht)
|
||||
this.formData.akadgrad_id = akadGradByGeschlecht.akadgrad_id;
|
||||
else
|
||||
this.formData.akadgrad_id = this.arrAkadGrad[0].akadgrad_id;
|
||||
}
|
||||
},
|
||||
printDocument(link) {
|
||||
@@ -471,29 +597,29 @@ export default {
|
||||
searchPerson(event) {
|
||||
if (this.abortController.persons) {
|
||||
this.abortController.persons.abort();
|
||||
}
|
||||
|
||||
}
|
||||
this.abortController.persons = new AbortController();
|
||||
|
||||
return this.$api
|
||||
.call(ApiStvAbschlusspruefung.getPruefer(event.query))
|
||||
.then(result => {
|
||||
this.filteredPersons = [];
|
||||
for (let person of result.data.retval) {
|
||||
this.filteredPersons.push(
|
||||
{
|
||||
label: this.getPersonLabel(
|
||||
person.titelpre,
|
||||
person.nachname,
|
||||
person.vorname,
|
||||
person.titelpost,
|
||||
person.person_uid
|
||||
),
|
||||
person_id: person.person_id
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
this.filteredPersons = [];
|
||||
for (let person of result.data.retval) {
|
||||
this.filteredPersons.push(
|
||||
{
|
||||
label: this.getPersonLabel(
|
||||
person.titelpre,
|
||||
person.nachname,
|
||||
person.vorname,
|
||||
person.titelpost,
|
||||
person.uid
|
||||
),
|
||||
person_id: person.person_id,
|
||||
mitarbeiter_uid: person.uid
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
@@ -526,7 +652,7 @@ export default {
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
this.$api
|
||||
.call(ApiStvAbschlusspruefung.getAkadGrade(this.student.studiengang_kz))
|
||||
.call(ApiStvAbschlusspruefung.getAkadGrade(this.stg_kz))
|
||||
.then(result => {
|
||||
this.arrAkadGrad = result.data;
|
||||
})
|
||||
@@ -547,7 +673,8 @@ export default {
|
||||
<div class="stv-details-abschlusspruefung h-100 pb-3">
|
||||
<h4>{{this.$p.t('stv','tab_finalexam')}}</h4>
|
||||
|
||||
<div v-if="this.student.length">
|
||||
<div v-if="this.student.length" class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @click="actionNewAbschlusspruefung()"> + {{$p.t('stv', 'tab_finalexam')}}</button>
|
||||
<abschlusspruefung-dropdown
|
||||
:showAllFormats="showAllFormats"
|
||||
:studentUids="studentUids"
|
||||
@@ -579,12 +706,19 @@ export default {
|
||||
<template #title>
|
||||
<p v-if="statusNew" class="fw-bold mt-3">{{$p.t('abschlusspruefung', 'abschluessPruefungAnlegen')}}</p>
|
||||
<p v-else class="fw-bold mt-3">{{$p.t('abschlusspruefung', 'abschluessPruefungBearbeiten')}}</p>
|
||||
<div
|
||||
v-if="this.student.length"
|
||||
class="stv-details-abschlusspruefung-student-names"
|
||||
>
|
||||
<small class="text-muted">{{studentNames}}</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<form-form v-if="!this.student.length" ref="formFinalExam" @submit.prevent>
|
||||
<form-form ref="formFinalExam" @submit.prevent>
|
||||
|
||||
<legend>{{this.$p.t('global','details')}}</legend>
|
||||
<p v-if="statusNew">[{{$p.t('ui', 'neu')}}]</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
container-class="col-6 stv-details-abschlusspruefung-typ"
|
||||
@@ -602,6 +736,7 @@ export default {
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
container-class="col-6 stv-details-abschlusspruefung-note"
|
||||
:label="$p.t('abschlusspruefung', 'notekommpruefung')"
|
||||
type="select"
|
||||
@@ -670,9 +805,10 @@ export default {
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
container-class="col-6 stv-details-abschlusspruefung-abschlussbeurteilung_kurzbz"
|
||||
:label="$p.t('abschlusspruefung', 'abschlussbeurteilung')"
|
||||
type="select"
|
||||
@@ -698,7 +834,23 @@ export default {
|
||||
optionValue="person_id"
|
||||
dropdown
|
||||
forceSelection
|
||||
:suggestions="filteredPersons"
|
||||
:suggestions="filteredPersons"
|
||||
@complete="searchPerson"
|
||||
:min-length="3"
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="this.student.length"
|
||||
type="autocomplete"
|
||||
container-class="col-6 stv-details-abschlusspruefung-pruefer3"
|
||||
:label="$p.t('abschlusspruefung', 'pruefer3')"
|
||||
name="pruefer3"
|
||||
v-model="selectedPruefer3"
|
||||
optionLabel="label"
|
||||
optionValue="person_id"
|
||||
dropdown
|
||||
forceSelection
|
||||
:suggestions="filteredPersons"
|
||||
@complete="searchPerson"
|
||||
:min-length="3"
|
||||
>
|
||||
@@ -718,10 +870,11 @@ export default {
|
||||
:key="grad.akadgrad_id"
|
||||
:value="grad.akadgrad_id"
|
||||
>
|
||||
{{grad.titel}}
|
||||
{{ grad.titel }} <span v-if="grad.geschlecht !== null"> ({{ grad.geschlecht }}) </span>
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
type="autocomplete"
|
||||
container-class="col-6 stv-details-abschlusspruefung-pruefer3"
|
||||
:label="$p.t('abschlusspruefung', 'pruefer3')"
|
||||
@@ -731,7 +884,7 @@ export default {
|
||||
optionValue="person_id"
|
||||
dropdown
|
||||
forceSelection
|
||||
:suggestions="filteredPersons"
|
||||
:suggestions="filteredPersons"
|
||||
@complete="searchPerson"
|
||||
:min-length="3"
|
||||
>
|
||||
@@ -779,6 +932,7 @@ export default {
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
container-class="col-6 stv-details-abschlusspruefung-protokoll"
|
||||
:label="$p.t('abschlusspruefung', 'protokoll')"
|
||||
type="textarea"
|
||||
@@ -790,7 +944,7 @@ export default {
|
||||
</form-input>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3 col-6">
|
||||
<div v-if="!this.student.length" class="row mb-3 col-6">
|
||||
<div class="col">
|
||||
<p >{{$p.t('abschlusspruefung', 'zurBeurteilung')}}</p>
|
||||
</div>
|
||||
@@ -807,8 +961,9 @@ export default {
|
||||
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{$p.t('ui', 'abbrechen')}}</button>
|
||||
<button v-if="statusNew" class="btn btn-primary" @click="addNewAbschlusspruefung()"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-else class="btn btn-primary" @click="updateAbschlusspruefung(formData.abschlusspruefung_id)"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-if="statusNew && !this.student.length" class="btn btn-primary" @click="addNewAbschlusspruefung()"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-else-if="statusNew && this.student.length" class="btn btn-primary" @click="addNewAbschlusspruefungMulti(studentUids)"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-else class="btn btn-primary" @click="updateAbschlusspruefung(formData.abschlusspruefung_id)"> {{$p.t('ui', 'speichern')}}</button>
|
||||
</template>
|
||||
|
||||
</bs-modal>
|
||||
|
||||
@@ -158,6 +158,16 @@ export default {
|
||||
},
|
||||
reload(){
|
||||
this.updateStudent(this.modelValue);
|
||||
},
|
||||
sendInfomail(){
|
||||
const subject = this.$p.t('person', 'betreffProfilfoto');
|
||||
const subjectEncoded = encodeURIComponent(subject);
|
||||
|
||||
const body = this.$p.t('person', 'mailText_profilfoto');
|
||||
const bodyWithNewLines = body.replace(/\\n/g, '\n');
|
||||
const bodyEncoded = encodeURIComponent(bodyWithNewLines);
|
||||
|
||||
window.location.href = "mailto:" + this.modelValue.mail_intern + "?subject=" + subjectEncoded + "&body=" + bodyEncoded;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -387,10 +397,12 @@ export default {
|
||||
container-class="col stv-details-details-foto"
|
||||
:label="$p.t('person', 'foto')"
|
||||
type="UploadImage"
|
||||
titleActionButton="Infomail"
|
||||
v-model="data.foto"
|
||||
name="foto"
|
||||
@actionbutton-clicked="sendInfomail"
|
||||
>
|
||||
<img alt="No Image" :src="noImageSrc" class="w-100">
|
||||
<img alt="No Image" :src="noImageSrc" />
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!config.hiddenFields.includes('anmerkung')"
|
||||
|
||||
@@ -20,7 +20,6 @@ export default {
|
||||
layoutColumnsOnNewData: false,
|
||||
height: 300,
|
||||
listDocuments: [],
|
||||
prestudentDocumentData: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -68,21 +67,36 @@ export default {
|
||||
let value = cell.getValue();
|
||||
let rowData = cell.getRow().getData(); // Zugriff auf gesamte Zeile
|
||||
let dueDate = rowData["nachgereicht_am"];
|
||||
const date = new Date(dueDate);
|
||||
let dateFormatted = date.toLocaleString("de-DE", {
|
||||
|
||||
let vorhanden = rowData["vorhanden"];
|
||||
let hochgeladenamum = vorhanden ? rowData["hochgeladenamum"] : '';
|
||||
|
||||
const dateDue = new Date(dueDate);
|
||||
let dateFormatted = dateDue.toLocaleString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour12: false
|
||||
});
|
||||
|
||||
let hochgeladenamumFormatted = hochgeladenamum
|
||||
? new Date(hochgeladenamum.replace(" ", "T")).toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
})
|
||||
: dateFormatted;
|
||||
|
||||
|
||||
const date = vorhanden && hochgeladenamum ? hochgeladenamumFormatted : dateFormatted;
|
||||
|
||||
let tickCrossIcon = value
|
||||
? '<i class="fa fa-check text-success"></i>'
|
||||
: '<i class="fa fa-xmark text-danger"></i>';
|
||||
|
||||
// if nachgereicht_am show datum
|
||||
// if nachgereicht_am show datum nachgereicht_am oder hochgeladenamum
|
||||
let pill = dueDate
|
||||
? `<span>${dateFormatted}</span>`
|
||||
? `<span>${date}</span>`
|
||||
: '';
|
||||
|
||||
return `${tickCrossIcon} ${pill}`;
|
||||
@@ -206,7 +220,7 @@ export default {
|
||||
setHeader('anmerkung_intern', this.$p.t('global', 'anmerkung'));
|
||||
setHeader('akte_id', this.$p.t('global', 'akte_id'));
|
||||
setHeader('pflicht', this.$p.t('ampeln', 'mandatory'));
|
||||
setHeader('nachgereicht_am', this.$p.t('global', 'dokument'));
|
||||
setHeader('nachgereicht_am', this.$p.t('dokumente', 'nachreichungAm'));
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { splitMailsHelper } from "../../../../helpers/EmailHelpers.js"
|
||||
import { splitMailsHelper } from "../../../../helpers/EmailHelpers.js";
|
||||
import { splitMailLinks } from "../../../../helpers/EmailHelpers.js";
|
||||
import FormInput from "../../../Form/Input.js";
|
||||
import FormForm from "../../../Form/Form.js";
|
||||
|
||||
export default {
|
||||
name: "Kontaktieren",
|
||||
components: {
|
||||
FormInput,
|
||||
FormForm
|
||||
},
|
||||
computed: {
|
||||
internMails() {
|
||||
if (this.modelValue.mail_intern)
|
||||
@@ -21,38 +29,150 @@ export default {
|
||||
props: {
|
||||
modelValue: Object
|
||||
},
|
||||
|
||||
inject: {
|
||||
authUid: {
|
||||
from: 'authUid',
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
mailLinks: [],
|
||||
mailLinksClicked: [],
|
||||
showMailDialog: false,
|
||||
showDivKomp: false,
|
||||
showDivKompLocalStorageId: 'studvw-contact-showDivKomp'
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
const showDivCompLocalStorage = window.localStorage.getItem(this.showDivKompLocalStorageId);
|
||||
this.showDivKomp = (showDivCompLocalStorage === 'true') ? true : false;
|
||||
},
|
||||
methods: {
|
||||
internMail(event) {
|
||||
if (this.internMails.length)
|
||||
{
|
||||
splitMailsHelper(this.internMails, event, null, null, this.$fhcAlert, this.$p)
|
||||
splitMailsHelper(this.internMails, event, null, null, this.$fhcAlert, this.$p, this.authUid)
|
||||
}
|
||||
},
|
||||
privateMail(event) {
|
||||
if (this.privateMails.length)
|
||||
{
|
||||
splitMailsHelper(this.privateMails, event, null,null, this.$fhcAlert, this.$p)
|
||||
splitMailsHelper(this.privateMails, event, null, null, this.$fhcAlert, this.$p, this.authUid)
|
||||
}
|
||||
},
|
||||
createMailLinks(event, mailGroup){
|
||||
let recipients = mailGroup == 'private' ? this.privateMails : this.internMails;
|
||||
this.mailLinks = splitMailLinks(
|
||||
recipients,
|
||||
"",
|
||||
event.ctrlKey || event.metaKey,
|
||||
this.authUid
|
||||
);
|
||||
this.mailLinksClicked = [];
|
||||
|
||||
this.showMailDialog = true;
|
||||
},
|
||||
handleMailLink(link, index){
|
||||
this.mailLinksClicked.push(index);
|
||||
window.open(link, '_blank', 'noopener');
|
||||
},
|
||||
reset(){
|
||||
this.showMailDialog = false;
|
||||
},
|
||||
onSwitchChange(){
|
||||
window.localStorage.setItem(this.showDivKompLocalStorageId, this.showDivKomp);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div id="elementID"></div>
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
<button class="btn btn-primary mb-2" @click="internMail($event)" :title="$p.t('stv', 'bccEMail')">
|
||||
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'internEMail')}}
|
||||
</button>
|
||||
<div class="core-kontaktieren">
|
||||
<div id="elementID"></div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-start pb-3">
|
||||
<a
|
||||
v-if="showMailDialog"
|
||||
class="btn btn-outline-secondary m-2"
|
||||
@click.prevent="reset"
|
||||
>
|
||||
<span class="fa-solid fa-delete-left" :title="this.$p.t('ui','zurueck')" ></span>
|
||||
|
||||
<span>{{ $p.t('ui','zurueck') }}</span>
|
||||
</a>
|
||||
<div v-else> </div>
|
||||
|
||||
<form-input
|
||||
container-class="form-switch mb-0"
|
||||
type="checkbox"
|
||||
:label="$p.t('ui/kompatibilityMode_WebmailClient')"
|
||||
v-model="showDivKomp"
|
||||
@change="onSwitchChange"
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
|
||||
<div v-if="!showDivKomp">
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
<button class="btn btn-primary mb-2" @click="internMail($event)" :title="$p.t('stv', 'bccEMail')">
|
||||
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'internEMail')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
<button class="btn btn-primary mb-2" @click="privateMail($event)" :title="$p.t('stv', 'bccEMail')">
|
||||
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'privateEMail')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
<button class="btn btn-primary mb-2" @click="privateMail($event)" :title="$p.t('stv', 'bccEMail')">
|
||||
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'privateEMail')}}
|
||||
</button>
|
||||
|
||||
<div v-if="showDivKomp">
|
||||
|
||||
<div v-if="!showMailDialog" class="row">
|
||||
<div class="col-lg-2">
|
||||
<button class="btn btn-secondary mb-2" @click="createMailLinks($event, 'intern')" :title="$p.t('stv', 'bccEMail')">
|
||||
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'internEMail')}} ({{$p.t('ui', 'webmailclient')}})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!showMailDialog" class="row">
|
||||
<div class="col-lg-2">
|
||||
<button class="btn btn-secondary mb-2" @click="createMailLinks($event, 'private')" :title="$p.t('stv', 'bccEMail')">
|
||||
<i class="fa-solid fa-mail"></i> {{$p.t('stv', 'privateEMail')}} ({{$p.t('ui', 'webmailclient')}})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showMailDialog" class="mt-3">
|
||||
<div v-if="mailLinks.length > 1" class="mt-2">
|
||||
<p>{{$p.t('stv', 'zuvieleEMails')}}</p>
|
||||
|
||||
<div
|
||||
v-for="(link,index) in mailLinks"
|
||||
:key="index"
|
||||
class="mb-2">
|
||||
<a
|
||||
:href="link"
|
||||
@click.prevent="handleMailLink(link, index)"
|
||||
>
|
||||
Email {{ index + 1 }}
|
||||
</a>
|
||||
<span v-show="mailLinksClicked.includes(index)"> <i class="fa-solid fa-check text-success"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a
|
||||
:href="mailLinks[0]"
|
||||
@click.prevent="handleMailLink(mailLinks[0], index)"
|
||||
class="btn btn-primary m-2"
|
||||
>
|
||||
{{$p.t('ui', 'openInMailClient')}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>`
|
||||
};
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import TableLvList from "./Lehrveranstaltungstermine/ListLehrveranstaltungstermine.js";
|
||||
import ApiStvCoursedates from "../../../../api/factory/stv/coursedates.js";
|
||||
|
||||
export default {
|
||||
name: "TabCourseList",
|
||||
@@ -8,11 +9,13 @@ export default {
|
||||
props: {
|
||||
modelValue: Object,
|
||||
},
|
||||
data(){
|
||||
return {}
|
||||
data() {
|
||||
return {
|
||||
endpoint: ApiStvCoursedates
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="stv-details-course-list h-100 d-flex flex-column">
|
||||
<table-lv-list ref="tbl_course_list" :student="modelValue"></table-lv-list>
|
||||
<table-lv-list ref="tbl_course_list" :id="modelValue.uid" :endpoint="endpoint"></table-lv-list>
|
||||
</div>`
|
||||
};
|
||||
+14
-13
@@ -2,8 +2,6 @@ import {CoreFilterCmpt} from "../../../../filter/Filter.js";
|
||||
import FormInput from "../../../../Form/Input.js";
|
||||
import FormForm from '../../../../Form/Form.js';
|
||||
|
||||
import ApiStvCoursedates from "../../../../../api/factory/stv/coursedates.js";
|
||||
|
||||
export default {
|
||||
name: "TblCourseList",
|
||||
components: {
|
||||
@@ -17,7 +15,14 @@ export default {
|
||||
},
|
||||
},
|
||||
props: {
|
||||
student: Object
|
||||
id: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
},
|
||||
endpoint: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
@@ -48,14 +53,7 @@ export default {
|
||||
initTabulatorOptions(){
|
||||
this.tabulatorOptions = {
|
||||
ajaxURL: 'dummy',
|
||||
ajaxRequestFunc: () => this.$api.call(
|
||||
ApiStvCoursedates.getCourselist({
|
||||
student_uid: this.student.uid,
|
||||
start_date: this.dataSem.start,
|
||||
end_date: this.dataSem.ende,
|
||||
group_consecutiveHours: true,
|
||||
dbStundenplanTable: this.dbStundenplanTable})
|
||||
),
|
||||
ajaxRequestFunc: () => this.$api.call(this.endpoint.getCourselist(this.id, this.dataSem.start, this.dataSem.ende, this.dbStundenplanTable)),
|
||||
ajaxResponse: (url, params, response) => {
|
||||
return response.data;
|
||||
},
|
||||
@@ -131,7 +129,7 @@ export default {
|
||||
this.dataSem = this.listStudiensemester.find(item => item.studiensemester_kurzbz === studiensemester_kurzbz);
|
||||
},
|
||||
exportToExcel(){
|
||||
window.open(this.downloadLink, '_blank');
|
||||
window.open(this.endpoint.exportCalendar(this.id, this.dbStundenplanTable), '_blank');
|
||||
},
|
||||
reload() {
|
||||
this.$refs.table.reloadTable();
|
||||
@@ -145,10 +143,13 @@ export default {
|
||||
currentSemester(newVal, oldVal) {
|
||||
this.getDatesOfSemester(newVal);
|
||||
},
|
||||
id() {
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.$api
|
||||
.call(ApiStvCoursedates.getStudiensemester())
|
||||
.call(this.endpoint.getStudiensemester())
|
||||
.then(result => {
|
||||
this.listStudiensemester = result.data;
|
||||
this.getDatesOfSemester(this.currentSemester);
|
||||
|
||||
@@ -2,6 +2,9 @@ import NotenZeugnis from './Noten/Zeugnis.js';
|
||||
import NotenTeacher from './Noten/Teacher.js';
|
||||
import NotenRepeater from './Noten/Repeater.js';
|
||||
|
||||
import ApiStvGrades from '../../../../api/factory/stv/grades.js';
|
||||
import { highlightGesamtnote } from "../../../../helpers/DocumentHelper.js";
|
||||
|
||||
const LOCAL_STORAGE_ID = 'stv_details_noten_2024-11-25_stdsem_all';
|
||||
|
||||
export default {
|
||||
@@ -22,9 +25,17 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
stdsem: ''
|
||||
stdsem: '',
|
||||
endpoint: ApiStvGrades,
|
||||
zeugnisLoaded: false,
|
||||
teacherLoaded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
gradeListLink() {
|
||||
return this.$api.getUri('/person/gradelist/index/' + this.modelValue.uid);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
this.$refs.zeugnis.$refs.table.reloadTable();
|
||||
@@ -33,26 +44,58 @@ export default {
|
||||
},
|
||||
saveStdsem(event) {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_ID, event.target.value);
|
||||
},
|
||||
onZeugnisLoaded() {
|
||||
this.zeugnisLoaded = true;
|
||||
this.checkHighlight();
|
||||
},
|
||||
onTeacherLoaded() {
|
||||
this.teacherLoaded = true;
|
||||
this.checkHighlight();
|
||||
},
|
||||
checkHighlight()
|
||||
{
|
||||
if (!this.zeugnisLoaded || !this.teacherLoaded)
|
||||
return;
|
||||
|
||||
if (!this.$refs.zeugnis || !this.$refs.teacher)
|
||||
return;
|
||||
|
||||
let zeugnisTable = this.$refs.zeugnis.$refs.table.tabulator;
|
||||
let teacherTable = this.$refs.teacher.$refs.table.tabulator;
|
||||
|
||||
if (!zeugnisTable || !teacherTable)
|
||||
return;
|
||||
|
||||
highlightGesamtnote(zeugnisTable, teacherTable);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const savedPath = window.localStorage.getItem(LOCAL_STORAGE_ID);
|
||||
this.stdsem = savedPath || '';
|
||||
},
|
||||
template: `
|
||||
template: /* html */`
|
||||
<div class="stv-details-noten d-flex flex-column overflow-hidden">
|
||||
<div class="mb-3">
|
||||
<select class="form-select" v-model="stdsem" @input="saveStdsem">
|
||||
<option value="">{{ $p.t('ui/current_semester') }}</option>
|
||||
<option value="true">{{ $p.t('ui/all_semester') }}</option>
|
||||
</select>
|
||||
<div class="d-flex justify-content-between my-2">
|
||||
<div>
|
||||
<select class="form-select" v-model="stdsem" @input="saveStdsem">
|
||||
<option value="">{{ $p.t('ui/current_semester') }}</option>
|
||||
<option value="true">{{ $p.t('ui/all_semester') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<a :href="gradeListLink" target="_blank">
|
||||
{{ $p.t('stv/grades_gradelist') }}
|
||||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<noten-zeugnis ref="zeugnis" :student="modelValue" :all-semester="!!stdsem"></noten-zeugnis>
|
||||
<noten-zeugnis ref="zeugnis" :id="modelValue.prestudent_id" :all-semester="!!stdsem" :endpoint="endpoint" @loaded="onZeugnisLoaded"></noten-zeugnis>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<noten-teacher ref="teacher" :student="modelValue" :all-semester="!!stdsem" @copied="reload"></noten-teacher>
|
||||
<noten-teacher ref="teacher" :id="modelValue.prestudent_id" :endpoint="endpoint" :all-semester="!!stdsem" @copied="reload" @loaded="onTeacherLoaded"></noten-teacher>
|
||||
<noten-repeater class="mt-4" ref="repeater" :student="modelValue" :all-semester="!!stdsem" @copied="reload"></noten-repeater>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
renderTabulator: false,
|
||||
tabulatorEvents: []
|
||||
};
|
||||
},
|
||||
@@ -118,13 +119,13 @@ export default {
|
||||
created() {
|
||||
this.$p.loadCategory(['global', 'stv', 'lehre', 'profil'])
|
||||
.then(() => {
|
||||
if (this.$refs.table.tableBuilt)
|
||||
this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns);
|
||||
this.renderTabulator = true
|
||||
});
|
||||
},
|
||||
template: `
|
||||
<div class="stv-details-noten-repeater d-flex flex-column">
|
||||
<core-filter-cmpt
|
||||
v-if="renderTabulator"
|
||||
ref="table"
|
||||
:title="$p.t('stv/grades_title_repeater')"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {CoreFilterCmpt} from "../../../../filter/Filter.js";
|
||||
|
||||
import ApiStvGrades from '../../../../../api/factory/stv/grades.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoreFilterCmpt
|
||||
},
|
||||
emits: [
|
||||
"copied"
|
||||
"copied",
|
||||
"loaded"
|
||||
],
|
||||
inject: {
|
||||
currentSemester: {
|
||||
@@ -16,20 +15,50 @@ export default {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
student: Object,
|
||||
allSemester: Boolean
|
||||
endpoint: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
},
|
||||
allSemester: Boolean,
|
||||
optionalTabulatorOptions: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabulatorEvents: []
|
||||
renderTabulator: false,
|
||||
setOptinalTabulatorOptionsVisibility: false,
|
||||
tabulatorEvents: [
|
||||
{
|
||||
event: "dataProcessed",
|
||||
handler: () => this.$emit("loaded"),
|
||||
},
|
||||
{
|
||||
event: "tableBuilt",
|
||||
handler: () => {
|
||||
if(this.optionalTabulatorOptions?.visibleColumns
|
||||
&& this.setOptinalTabulatorOptionsVisibility)
|
||||
{
|
||||
for(let key in this.optionalTabulatorOptions.visibleColumns) {
|
||||
if(this.optionalTabulatorOptions.visibleColumns[key])
|
||||
this.$refs.table.tabulator.showColumn(key);
|
||||
else
|
||||
this.$refs.table.tabulator.hideColumn(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tabulatorOptions() {
|
||||
return {
|
||||
ajaxURL: 'dummy',
|
||||
ajaxRequestFunc: () => this.$api.call(ApiStvGrades.getTeacherProposal(
|
||||
this.student.prestudent_id,
|
||||
ajaxRequestFunc: () => this.$api.call(this.endpoint.getTeacherProposal(
|
||||
this.id,
|
||||
(!this.allSemester ? this.currentSemester : null)
|
||||
)),
|
||||
ajaxResponse: (url, params, response) => {
|
||||
@@ -74,19 +103,24 @@ export default {
|
||||
{ field: 'studiensemester_kurzbz', title: this.$p.t('lehre/studiensemester'), visible: false },
|
||||
{ field: 'note', title: this.$p.t('stv/grades_numericgrade'), visible: false },
|
||||
{ field: 'student_uid', title: this.$p.t('profil/studentIn'), visible: false },
|
||||
{ field: 'vorname', title: this.$p.t('person/vorname'), visible: false },
|
||||
{ field: 'nachname', title: this.$p.t('person/nachname'), visible: false },
|
||||
{ field: 'lehrveranstaltung_id', title: this.$p.t('lehre/lehrveranstaltung_id'), visible: false },
|
||||
{ field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false }
|
||||
],
|
||||
columnDefaults: {
|
||||
headerFilter: this.optionalTabulatorOptions?.headerFilter ?? false,
|
||||
},
|
||||
layout: 'fitDataStretch',
|
||||
height: '100%',
|
||||
selectableRows: true,
|
||||
selectableRowsRangeMode: 'click',
|
||||
persistenceID: 'stv-details-noten-teacher-20260217'
|
||||
persistenceID: this.optionalTabulatorOptions?.persistenceTeacherID ?? 'stv-details-noten-teacher-2025120401',
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
student(n) {
|
||||
id() {
|
||||
this.$refs.table.reloadTable();
|
||||
},
|
||||
allSemester(n) {
|
||||
@@ -97,7 +131,7 @@ export default {
|
||||
copyGrades(selected) {
|
||||
const promises = selected.map(
|
||||
grade => this.$api
|
||||
.call(ApiStvGrades.copyTeacherProposalToCertificate(grade), {
|
||||
.call(this.endpoint.copyTeacherProposalToCertificate(grade), {
|
||||
errorHeader: grade.lehrveranstaltung_bezeichnung
|
||||
})
|
||||
.then(() => {
|
||||
@@ -117,13 +151,19 @@ export default {
|
||||
created() {
|
||||
this.$p.loadCategory(['stv', 'lehre', 'profil'])
|
||||
.then(() => {
|
||||
if (this.$refs.table.tableBuilt)
|
||||
this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns);
|
||||
this.renderTabulator = true;
|
||||
});
|
||||
|
||||
if(this?.optionalTabulatorOptions)
|
||||
{
|
||||
const localStorageKey = 'tabulator-' + this.optionalTabulatorOptions.persistenceTeacherID + '-columns';
|
||||
this.setOptinalTabulatorOptionsVisibility = (window.localStorage.getItem(localStorageKey) === null) ? true : false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="stv-details-noten-teacher d-flex flex-column">
|
||||
<core-filter-cmpt
|
||||
v-if="renderTabulator"
|
||||
ref="table"
|
||||
:title="$p.t('stv/grades_title_teacher')"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
|
||||
@@ -2,8 +2,6 @@ import {CoreFilterCmpt} from "../../../../filter/Filter.js";
|
||||
import ZeugnisActions from './Zeugnis/Actions.js';
|
||||
import ZeugnisDocuments from './Zeugnis/Documents.js';
|
||||
|
||||
import ApiStvGrades from '../../../../../api/factory/stv/grades.js';
|
||||
|
||||
export default {
|
||||
name: 'Zeugnis',
|
||||
components: {
|
||||
@@ -11,6 +9,7 @@ export default {
|
||||
ZeugnisActions,
|
||||
ZeugnisDocuments
|
||||
},
|
||||
emits: ['loaded'],
|
||||
inject: {
|
||||
config: {
|
||||
from: 'config',
|
||||
@@ -22,11 +21,21 @@ export default {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
student: Object,
|
||||
endpoint: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
},
|
||||
optionalTabulatorOptions: Object,
|
||||
allSemester: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
renderTabulator: false,
|
||||
setOptinalTabulatorOptionsVisibility: false,
|
||||
tabulatorEvents: [
|
||||
{
|
||||
event: "dataLoaded",
|
||||
@@ -35,6 +44,10 @@ export default {
|
||||
return item;
|
||||
})
|
||||
},
|
||||
{
|
||||
event: "dataProcessed",
|
||||
handler: () => this.$emit("loaded")
|
||||
},
|
||||
{
|
||||
event: "rowSelected",
|
||||
handler: row => row.getElement().style.zIndex = 12
|
||||
@@ -42,6 +55,21 @@ export default {
|
||||
{
|
||||
event: "rowDeselected",
|
||||
handler: row => row.getElement().style.zIndex = ''
|
||||
},
|
||||
{
|
||||
event: "tableBuilt",
|
||||
handler: () => {
|
||||
if(this.optionalTabulatorOptions?.visibleColumns
|
||||
&& this.setOptinalTabulatorOptionsVisibility)
|
||||
{
|
||||
for(let key in this.optionalTabulatorOptions.visibleColumns) {
|
||||
if(this.optionalTabulatorOptions.visibleColumns[key])
|
||||
this.$refs.table.tabulator.showColumn(key);
|
||||
else
|
||||
this.$refs.table.tabulator.hideColumn(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
stdsem: '',
|
||||
@@ -53,7 +81,7 @@ export default {
|
||||
computed: {
|
||||
tabulatorOptions() {
|
||||
const listPromise = this.$api
|
||||
.call(ApiStvGrades.list())
|
||||
.call(this.endpoint.list())
|
||||
.then(res => res.data.map(({bezeichnung: label, note: value}) => ({label, value})));
|
||||
|
||||
let gradeField = {
|
||||
@@ -86,7 +114,7 @@ export default {
|
||||
note_bezeichnung
|
||||
}))
|
||||
// send to backend
|
||||
.then(data => this.$api.call(ApiStvGrades.updateCertificate(data)))
|
||||
.then(data => this.$api.call(this.endpoint.updateCertificate(data)))
|
||||
// get bezeichnung again
|
||||
.then(() => listPromise)
|
||||
.then(list => list.find(el => el.value == note))
|
||||
@@ -109,7 +137,7 @@ export default {
|
||||
if (filterTerm) {
|
||||
return this.$api
|
||||
.call(
|
||||
ApiStvGrades.getGradeFromPoints(
|
||||
this.endpoint.getGradeFromPoints(
|
||||
filterTerm,
|
||||
cell.getData().lehrveranstaltung_id,
|
||||
this.currentSemester
|
||||
@@ -142,6 +170,8 @@ export default {
|
||||
.then(() => node.innerText = this.$p.t('ui/loading'))
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
gradeField.editorParams.placeholderLoading = node;
|
||||
gradeField.visible = this.optionalTabulatorOptions?.visibleColumns?.note ?? true
|
||||
gradeField.headerFiler = this.optionalTabulatorOptions?.headerFilter?.note || this.optionalTabulatorOptions?.note || false
|
||||
}
|
||||
|
||||
const columns = [
|
||||
@@ -192,7 +222,10 @@ export default {
|
||||
{ field: 'lehrform', title: this.$p.t('lehre/lehrform'), visible: false },
|
||||
{ field: 'kurzbz', title: this.$p.t('lehre/kurzbz'), visible: false },
|
||||
{ field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false },
|
||||
{ field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false }
|
||||
{ field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false },
|
||||
{ field: 'uid', title: this.$p.t('person/uid'), visible: false },
|
||||
{ field: 'vorname', title: this.$p.t('person/vorname'), visible: false },
|
||||
{ field: 'nachname', title: this.$p.t('person/nachname'), visible: false }
|
||||
];
|
||||
|
||||
const hasDocuments = ['both', 'inline'].includes(this.config.documents);
|
||||
@@ -202,6 +235,7 @@ export default {
|
||||
columns.push({
|
||||
field: 'actions',
|
||||
title: this.$p.t('global/actions'),
|
||||
minWidth: 120,
|
||||
cssClass: "overflow-visible",
|
||||
headerSort: false,
|
||||
formatter: cell => {
|
||||
@@ -221,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();
|
||||
@@ -238,8 +272,8 @@ export default {
|
||||
|
||||
return {
|
||||
ajaxURL: 'dummy',
|
||||
ajaxRequestFunc: () => this.$api.call(ApiStvGrades.getCertificate(
|
||||
this.student.prestudent_id,
|
||||
ajaxRequestFunc: () => this.$api.call(this.endpoint.getCertificate(
|
||||
this.id,
|
||||
(!this.allSemester ? this.currentSemester : null)
|
||||
)),
|
||||
ajaxResponse: (url, params, response) => {
|
||||
@@ -248,9 +282,10 @@ export default {
|
||||
columns,
|
||||
height: '100%',
|
||||
layout: 'fitDataStretchFrozen',
|
||||
selectableRows: 1,
|
||||
selectable: true,
|
||||
selectableRowsRangeMode: 'click',
|
||||
persistenceID: 'stv-details-noten-zeugnis-20260217',
|
||||
selectableRows: true,
|
||||
persistenceID: this.optionalTabulatorOptions?.persistenceZeugnisID ?? 'stv-details-noten-zeugnis-20260217',
|
||||
persistence:{
|
||||
columns: ["width", "visible", "frozen"]
|
||||
}
|
||||
@@ -258,7 +293,8 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
student(n) {
|
||||
id()
|
||||
{
|
||||
this.$refs.table.reloadTable();
|
||||
},
|
||||
allSemester(n) {
|
||||
@@ -269,7 +305,7 @@ export default {
|
||||
setGrade(data) {
|
||||
this.$api
|
||||
.call(
|
||||
ApiStvGrades.updateCertificate(data),
|
||||
this.endpoint.updateCertificate(data),
|
||||
{ errorHeader: data.lehrveranstaltung_bezeichnung }
|
||||
)
|
||||
.then(this.$refs.table.reloadTable)
|
||||
@@ -282,7 +318,7 @@ export default {
|
||||
.confirmDelete()
|
||||
.then(result => result ? data : Promise.reject({handled:true}))
|
||||
.then(data => this.$api.call(
|
||||
ApiStvGrades.deleteCertificate(data),
|
||||
this.endpoint.deleteCertificate(data),
|
||||
{ errorHeader: data.lehrveranstaltung_bezeichnung }
|
||||
))
|
||||
.then(this.$refs.table.reloadTable)
|
||||
@@ -291,15 +327,21 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$p.loadCategory(['global', 'stv', 'lehre'])
|
||||
this.$p.loadCategory(['global', 'stv', 'lehre', 'person'])
|
||||
.then(() => {
|
||||
if (this.$refs.table.tableBuilt)
|
||||
this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns);
|
||||
this.renderTabulator = true;
|
||||
});
|
||||
|
||||
if(this?.optionalTabulatorOptions)
|
||||
{
|
||||
const localStorageKey = 'tabulator-' + this.optionalTabulatorOptions.persistenceZeugnisID + '-columns';
|
||||
this.setOptinalTabulatorOptionsVisibility = (window.localStorage.getItem(localStorageKey) === null) ? true : false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="stv-details-noten-zeugnis h-100 d-flex flex-column">
|
||||
<core-filter-cmpt
|
||||
v-if="renderTabulator"
|
||||
ref="table"
|
||||
:title="$p.t('stv/grades_title_zeugnis')"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
|
||||
@@ -8,7 +8,11 @@ export default {
|
||||
CoreNotiz
|
||||
},
|
||||
props: {
|
||||
modelValue: Object
|
||||
modelValue: Object,
|
||||
config: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -23,7 +27,7 @@ export default {
|
||||
class="overflow-hidden"
|
||||
:endpoint="endpoint"
|
||||
ref="formc"
|
||||
notiz-layout="popupModal"
|
||||
:notiz-layout="config.notizLayout"
|
||||
type-id="person_id"
|
||||
:id="modelValue.person_id"
|
||||
show-document
|
||||
@@ -31,6 +35,7 @@ export default {
|
||||
:visibleColumns="['titel','text','verfasser','bearbeiter','dokumente']"
|
||||
@reload="$emit('update:suffix')"
|
||||
tabulator-persistence-id="stv-notiz-20260217"
|
||||
:show-erweitert="config.showErweitert"
|
||||
>
|
||||
</core-notiz>
|
||||
|
||||
@@ -49,16 +54,16 @@ ref="formc"
|
||||
type-id: id to which table the notizdata should be connected... eg. person_id, prestudent_id, uid (for mitarbeiter_uid), projekt_kurzbz, projektphase_id, projekttask_id,
|
||||
bestellung_id, lehreinheit_id, anrechnung_id
|
||||
|
||||
notizLayout: "classicFas", "twoColumnsFormLeft", twoColumnsFormRight, popupModal"
|
||||
notizLayout: "classicFas", "twoColumnsFormLeft", twoColumnsFormRight, popupModal"; is now configurable in config/stv.php
|
||||
|
||||
showErweitert: if true: section with following fields will be displayed:
|
||||
'verfasser', 'bearbeiter', 'von', 'bis'
|
||||
'verfasser', 'bearbeiter', 'von', 'bis' and 'erledigt'; is now configurable in config/stv.php
|
||||
|
||||
showDocument: if true: section with documentHandling will be displayed
|
||||
|
||||
showTinyMCE: if true: section with WYSIWYG Editor for Text will be displayed
|
||||
|
||||
visibleColumns: list, which fields shoult be showed as default in filter component
|
||||
visibleColumns: list, which fields should be showed as default in filter component
|
||||
fullVersion: :visibleColumns="['titel','text','bearbeiter','verfasser','von','bis','dokumente','erledigt','notiz_id','notizzuordnung_id','id','lastupdate']"
|
||||
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ export default {
|
||||
return this.$api
|
||||
.call(ApiStvProjektarbeit.getLehrveranstaltungen(
|
||||
this.student.uid,
|
||||
this.newProjektarbeit ? this.student.studiengang_kz : null,
|
||||
this.student.studiengang_kz,
|
||||
this.studiensemester,
|
||||
this.additional_lehrveranstaltung_id
|
||||
))
|
||||
@@ -227,7 +227,8 @@ export default {
|
||||
if (preparedFormData.projektarbeit_id == null) delete(preparedFormData.projektarbeit_id);
|
||||
delete(preparedFormData.firma);
|
||||
delete(preparedFormData.firma_name);
|
||||
delete(preparedFormData.lehrveranstaltung_id);
|
||||
|
||||
preparedFormData.studiensemester_kurzbz = this.studiensemester
|
||||
|
||||
return preparedFormData;
|
||||
}
|
||||
@@ -290,7 +291,7 @@ export default {
|
||||
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-10">
|
||||
<div class="col-9">
|
||||
<form-input
|
||||
container-class="stv-details-projektarbeit-firma"
|
||||
:label="$p.t('projektarbeit', 'firma')"
|
||||
@@ -304,7 +305,7 @@ export default {
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
<div class="col-2 align-content-center">
|
||||
<div class="col-3 align-content-center">
|
||||
<a :href="firmenverwaltungLink" target="_blank">
|
||||
{{ $p.t('projektarbeit', 'zurFirmenverwaltung') }}
|
||||
</a>
|
||||
@@ -313,14 +314,13 @@ export default {
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
container-class="stv-details-projektarbeit-lv col-10"
|
||||
container-class="stv-details-projektarbeit-lv col-9"
|
||||
:label="$p.t('projektarbeit', 'lehrveranstaltung')"
|
||||
type="select"
|
||||
v-model="formData.lehrveranstaltung_id"
|
||||
name="lehrveranstaltung_id"
|
||||
@change="lvChanged($event)"
|
||||
>
|
||||
<option :value="null"> -- {{$p.t('fehlermonitoring', 'keineAuswahl')}} -- </option>
|
||||
<option
|
||||
v-for="lv in arrLvs"
|
||||
:key="lv.lehrveranstaltung_id"
|
||||
@@ -330,14 +330,13 @@ export default {
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
container-class="col-2"
|
||||
container-class="col-3"
|
||||
:label="$p.t('lehre', 'studiensemester')"
|
||||
type="select"
|
||||
v-model="studiensemester"
|
||||
name="studiensemester"
|
||||
@change="studiensemesterChanged"
|
||||
>
|
||||
<option :value="null"> -- {{$p.t('fehlermonitoring', 'keineAuswahl')}} -- </option>
|
||||
<option
|
||||
v-for="sem in arrStudiensemester"
|
||||
:key="sem.studiensemester_kurzbz"
|
||||
|
||||
+10
-10
@@ -42,16 +42,16 @@ export default {
|
||||
return {
|
||||
tabulatorOptions: {
|
||||
columns: [
|
||||
{title: "Nachname", field: "nachname"},
|
||||
{title: "Vorname", field: "vorname"},
|
||||
{title: "Note", field: "note"},
|
||||
{title: "Punkte", field: "punkte"},
|
||||
{title: "Stunden", field: "stunden"},
|
||||
{title: "Stundensatz", field: "stundensatz", visible: false},
|
||||
{title: "Art", field: "betreuerart_kurzbz", visible: false},
|
||||
{title: "Person ID", field: "person_id", visible: false},
|
||||
{title: "Vertrag ID", field: "vertrag_id", visible: false},
|
||||
{title: "Projektarbeit ID", field: "projektarbeit_id", visible: false},
|
||||
{title: "Nachname", field: "nachname", widthGrow: 2},
|
||||
{title: "Vorname", field: "vorname", widthGrow: 2},
|
||||
{title: "Note", field: "note", widthGrow: 1},
|
||||
{title: "Punkte", field: "punkte", widthGrow: 1},
|
||||
{title: "Stunden", field: "stunden", widthGrow: 1},
|
||||
{title: "Stundensatz", field: "stundensatz", visible: false, widthGrow: 1},
|
||||
{title: "Art", field: "betreuerart_kurzbz", visible: false, widthGrow: 2},
|
||||
{title: "Person ID", field: "person_id", visible: false, widthGrow: 1},
|
||||
{title: "Vertrag ID", field: "vertrag_id", visible: false, widthGrow: 1},
|
||||
{title: "Projektarbeit ID", field: "projektarbeit_id", visible: false, widthGrow: 1},
|
||||
{
|
||||
title: 'Aktionen',
|
||||
field: 'actions',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
import BsModal from "../../../Bootstrap/Modal.js";
|
||||
import FormForm from "../../../Form/Form.js";
|
||||
|
||||
export default {
|
||||
name: "modalLoading",
|
||||
components: {
|
||||
BsModal,
|
||||
FormForm,
|
||||
PvProgressBar: primevue.progressbar
|
||||
},
|
||||
props: {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
progress: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
processed: {
|
||||
type: Number,
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return{}
|
||||
},
|
||||
methods:{
|
||||
open(){
|
||||
this.$refs.modalLoading.show();
|
||||
},
|
||||
closeModal(){
|
||||
this.$refs.modalLoading.hide();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<bs-modal
|
||||
ref="modalLoading"
|
||||
noCloseBtn
|
||||
backdrop='static'
|
||||
:keyboard='false'
|
||||
dialog-class="modal-dialog-scrollable"
|
||||
>
|
||||
<template #title>
|
||||
{{ $p.t('ui/loading') }}
|
||||
</template>
|
||||
|
||||
<form-form
|
||||
ref="formModal"
|
||||
>
|
||||
<div class="mt-3">
|
||||
<div v-if="isLoading">
|
||||
{{message}}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{$p.t('ui', 'fenster_schliessen')}}
|
||||
</div>
|
||||
</div>
|
||||
</form-form>
|
||||
|
||||
<pv-progress-bar v-if="progress >= 0" class="my-3" :value="progress">{{processed}} / {{total}}</pv-progress-bar>
|
||||
|
||||
<template #footer>
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="isLoading"
|
||||
@click="closeModal()">
|
||||
{{$p.t('ui', 'schliessen')}}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</bs-modal>
|
||||
`,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {CoreRESTClient} from '../../../RESTClient.js';
|
||||
import ApiStudiensemester from '../../../api/factory/studiensemester.js';
|
||||
|
||||
export default {
|
||||
emits: [
|
||||
@@ -18,6 +18,11 @@ export default {
|
||||
today: -1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
lowerCaseList() {
|
||||
return this.list.map(i => i.toLowerCase());
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
set(n) {
|
||||
this.loading = true;
|
||||
@@ -47,25 +52,18 @@ export default {
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
CoreRESTClient
|
||||
.get('components/stv/studiensemester/now')
|
||||
.then(result => CoreRESTClient.getData(result.data))
|
||||
this.$api
|
||||
.call(ApiStudiensemester.current())
|
||||
.then(result => {
|
||||
this.today = this.list.indexOf(result);
|
||||
if (this.today >= 0) {
|
||||
if (this.today != this.current)
|
||||
this.set(this.today);
|
||||
} else {
|
||||
// TODO(chris): handle error (list might not be loaded yet)
|
||||
}
|
||||
this.today = this.list.indexOf(result.data);
|
||||
if (this.today >= 0 && this.today != this.current)
|
||||
this.set(this.today);
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
save(fallback) {
|
||||
CoreRESTClient
|
||||
.post('components/stv/studiensemester/set', {
|
||||
studiensemester: this.list[this.current]
|
||||
})
|
||||
this.$api
|
||||
.call(ApiStudiensemester.set(this.list[this.current]))
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
this.$emit('update:studiensemesterKurzbz', this.list[this.current]);
|
||||
@@ -73,27 +71,42 @@ export default {
|
||||
.catch(error => {
|
||||
this.current = fallback;
|
||||
this.loading = false;
|
||||
this.$fhcAlert.handleFormValidation(error);
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
},
|
||||
index() {
|
||||
let index = this.list.indexOf(this.studiensemesterKurzbz);
|
||||
if (index >= 0)
|
||||
return index;
|
||||
|
||||
// check if input is lower cases (because it came from an url)
|
||||
index = this.lowerCaseList.indexOf(this.studiensemesterKurzbz);
|
||||
if (index >= 0) {
|
||||
this.$emit('update:studiensemesterKurzbz', this.list[index]);
|
||||
return index;
|
||||
}
|
||||
|
||||
this.$emit('update:studiensemesterKurzbz', null);
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'studiensemesterKurzbz': function () {
|
||||
this.current = this.list.indexOf(this.studiensemesterKurzbz);
|
||||
studiensemesterKurzbz() {
|
||||
if (!this.loading)
|
||||
this.current = this.index(this.studiensemesterKurzbz);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
CoreRESTClient
|
||||
.get('components/stv/studiensemester')
|
||||
.then(result => CoreRESTClient.getData(result.data) || [])
|
||||
this.$api
|
||||
.call(ApiStudiensemester.getAll())
|
||||
.then(result => {
|
||||
this.list = result.map(el => el.studiensemester_kurzbz);
|
||||
this.list = result.data.map(el => el.studiensemester_kurzbz);
|
||||
this.loading = false;
|
||||
this.current = this.list.indexOf(this.studiensemesterKurzbz);
|
||||
this.current = this.index(this.studiensemesterKurzbz);
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
template: `
|
||||
template: /* html */`
|
||||
<div class="stv-studiensemester">
|
||||
<div class="btn-toolbar w-100 dropup" role="toolbar" aria-label="Studiensemester">
|
||||
<div class="btn-group flex-grow-1 position-static" role="group" aria-label="Studiensemester einstellen">
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import drop from '../../../directives/drop.js';
|
||||
import dragClick from '../../../directives/dragClick.js';
|
||||
import BaseTreemenu from '../../Base/Treemenu.js';
|
||||
|
||||
import ApiStvGroups from '../../../api/factory/stv/group.js';
|
||||
import ApiStvDetails from '../../../api/factory/stv/details.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PvTreetable: primevue.treetable,
|
||||
PvColumn: primevue.column
|
||||
},
|
||||
directives: {
|
||||
drop,
|
||||
dragClick
|
||||
BaseTreemenu
|
||||
},
|
||||
inject: {
|
||||
$reloadList: {
|
||||
@@ -33,230 +27,22 @@ export default {
|
||||
'selectVerband'
|
||||
],
|
||||
props: {
|
||||
endpoint: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
preselectedKey: {
|
||||
type: String,
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
nodes: [],
|
||||
selectedKey: [],
|
||||
expandedKeys: {},
|
||||
filters: {}, // TODO(chris): filter only 1st level?
|
||||
favorites: {on: false, list: []}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredNodes() {
|
||||
if (this.favorites.on)
|
||||
return this.nodes.filter(node => this.favorites.list.includes(node.key));
|
||||
|
||||
return this.nodes;
|
||||
},
|
||||
noSemReloadNodes() {
|
||||
return this.nodes.reduce(this.mapNodesToNoSemReloadNodes, []);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'preselectedKey': function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.setPreselection();
|
||||
}
|
||||
},
|
||||
'appConfig.number_displayed_past_studiensemester'(newVal, oldVal) {
|
||||
if (oldVal !== undefined) {
|
||||
this.noSemReloadNodes.forEach(node => {
|
||||
delete node.children;
|
||||
this.onExpandTreeNode(node);
|
||||
});
|
||||
if (oldVal !== undefined && this.$refs.menu) {
|
||||
this.$refs.menu.reloadNodesWithProp('no_sem_reload');
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
findNodeByKey(key, arr) {
|
||||
if (!arr)
|
||||
arr = this.nodes;
|
||||
let res = arr.filter(n => n.key == key);
|
||||
if (res.length)
|
||||
return res.pop();
|
||||
res = arr.map(n => n.children ? this.findNodeByKey(key, n.children) : null).filter(a => a);
|
||||
if (res.length)
|
||||
return res.pop();
|
||||
return null;
|
||||
},
|
||||
async onExpandTreeNode(node) {
|
||||
if (!node.children) {
|
||||
if (node.data.link) {
|
||||
let activeEl = null;
|
||||
this.$nextTick(() => {
|
||||
this.$nextTick(() => {
|
||||
activeEl = document.activeElement;
|
||||
});
|
||||
});
|
||||
this.loading = true;
|
||||
|
||||
return this.$api
|
||||
.call(this.endpoint.get(node.data.link))
|
||||
.then(result => result.data)
|
||||
.then(result => {
|
||||
const subNodes = result.map(this.mapResultToTreeData);
|
||||
const realNode = this.findNodeByKey(node.key);
|
||||
if (realNode)
|
||||
realNode.children = subNodes;
|
||||
else
|
||||
node.children = subNodes; // NOTE(chris): fallback should never be the case
|
||||
|
||||
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', {
|
||||
code: 'ArrowDown',
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
});
|
||||
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
}
|
||||
}
|
||||
},
|
||||
onSelectTreeNode(node) {
|
||||
if (node.data.link)
|
||||
this.$emit('selectVerband', {link: node.data.link, studiengang_kz: node.data.stg_kz, semester: node.data.semester, orgform_kurzbz: node.data.orgform_kurzbz});
|
||||
},
|
||||
mapNodesToNoSemReloadNodes(result, node) {
|
||||
if (node.data.no_sem_reload)
|
||||
result.push(node);
|
||||
if (node.children)
|
||||
result = node.children.reduce(this.mapNodesToNoSemReloadNodes, result);
|
||||
return result;
|
||||
},
|
||||
mapResultToTreeData(el) {
|
||||
const cp = {
|
||||
key: ("" + el.link).replace('/', '-'),
|
||||
data: el,
|
||||
label: el.name
|
||||
};
|
||||
|
||||
if (el.children)
|
||||
cp.children = el.children.map(this.mapResultToTreeData);
|
||||
else
|
||||
cp.leaf = el.leaf || false;
|
||||
|
||||
return cp;
|
||||
},
|
||||
filterFav() {
|
||||
this.favorites.on = !this.favorites.on;
|
||||
this.$api
|
||||
.call(this.endpoint.favorites.set(
|
||||
JSON.stringify(this.favorites)
|
||||
));
|
||||
},
|
||||
markFav(key) {
|
||||
let index = this.favorites.list.indexOf(key.data.link + '');
|
||||
|
||||
if (index != -1) {
|
||||
this.favorites.list.splice(index, 1);
|
||||
} else {
|
||||
this.favorites.list.push(key.data.link + '');
|
||||
}
|
||||
|
||||
this.$api
|
||||
.call(this.endpoint.favorites.set(
|
||||
JSON.stringify(this.favorites)
|
||||
));
|
||||
},
|
||||
unsetFavFocus(e) {
|
||||
if (e.target.dataset?.linkFavAdd !== undefined) {
|
||||
e.target.tabIndex = -1;
|
||||
} else {
|
||||
let items = e.target.querySelectorAll('[data-link-fav-add]:not([tabindex="-1"])');
|
||||
items.forEach(el => el.tabIndex = document.activeElement == el ? 0 : -1);
|
||||
}
|
||||
},
|
||||
setFavFocus(e) {
|
||||
if (e.target.dataset?.linkFavAdd !== undefined) {
|
||||
e.target.tabIndex = 0;
|
||||
} else {
|
||||
let items = e.target.querySelectorAll('[data-link-fav-add][tabindex="-1"]');
|
||||
items.forEach(el => el.tabIndex = 0);
|
||||
}
|
||||
},
|
||||
async setPreselection()
|
||||
{
|
||||
if (!this.preselectedKey)
|
||||
{
|
||||
this.selectedKey = null;
|
||||
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;
|
||||
|
||||
if(this.selectedKey)
|
||||
{
|
||||
const currentSelectedKey = Object.keys(this.selectedKey).find(Boolean);
|
||||
if (currentSelectedKey) {
|
||||
if (currentSelectedKey == currentKey)
|
||||
return;
|
||||
/**
|
||||
* Do not select a new entry if the current is a child of the new one.
|
||||
* This happens if a child entry of a new stg is selected and the router
|
||||
* tries to select the stg root entry (because subtrees do not have
|
||||
* routes yet)
|
||||
*/
|
||||
const isChild = this.findNodeByKey(
|
||||
currentSelectedKey,
|
||||
currentNode.children || []
|
||||
);
|
||||
if (isChild)
|
||||
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);
|
||||
},
|
||||
async toggleTreeNode(node) {
|
||||
if (this.expandedKeys[node.key]) {
|
||||
delete this.expandedKeys[node.key];
|
||||
} else if (!node.leaf) {
|
||||
await this.onExpandTreeNode(node);
|
||||
this.expandedKeys[node.key] = true;
|
||||
}
|
||||
if (node.path)
|
||||
this.$emit('selectVerband', {link: node.path, studiengang_kz: node.stg_kz, semester: node.semester, orgform_kurzbz: node.orgform_kurzbz});
|
||||
},
|
||||
getStudentAjaxId(student) {
|
||||
let res = student.id;
|
||||
@@ -264,23 +50,22 @@ export default {
|
||||
res += ' (' + student.vorname + ' ' + student.nachname + ')';
|
||||
return res;
|
||||
},
|
||||
dropStudents(node, students) {
|
||||
const data = node.data;
|
||||
|
||||
onDrop({ drag, drop }) {
|
||||
let endpoint;
|
||||
if (data.gruppe_kurzbz) {
|
||||
endpoint = students.map(student => [
|
||||
|
||||
if (drop.gruppe_kurzbz) {
|
||||
endpoint = drag.map(student => [
|
||||
this.getStudentAjaxId(student),
|
||||
ApiStvGroups.add(
|
||||
student.id,
|
||||
data.gruppe_kurzbz,
|
||||
drop.gruppe_kurzbz,
|
||||
this.currentSemester
|
||||
)
|
||||
]);
|
||||
} else {
|
||||
const { semester, verband, gruppe } = data;
|
||||
const { semester, verband, gruppe } = drop;
|
||||
const params = { semester, verband, gruppe };
|
||||
endpoint = students.map(student => [
|
||||
endpoint = drag.map(student => [
|
||||
this.getStudentAjaxId(student),
|
||||
ApiStvDetails.saveStudent(
|
||||
student.id,
|
||||
@@ -296,117 +81,14 @@ export default {
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$api
|
||||
.call(this.endpoint.get())
|
||||
.then(result => {
|
||||
this.nodes = result.data.map(el => {
|
||||
el.root = true;
|
||||
return this.mapResultToTreeData(el);
|
||||
});
|
||||
this.setPreselection();
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
this.$api
|
||||
.call(this.endpoint.favorites.get())
|
||||
.then(result => {
|
||||
if (result.data) {
|
||||
this.favorites = JSON.parse(result.data);
|
||||
}
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="overflow-auto" tabindex="-1">
|
||||
<pv-treetable
|
||||
ref="tree"
|
||||
class="stv-verband p-treetable-sm"
|
||||
:value="filteredNodes"
|
||||
@node-expand="onExpandTreeNode"
|
||||
selection-mode="single"
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
v-model:selection-keys="selectedKey"
|
||||
@node-select="onSelectTreeNode"
|
||||
scrollable
|
||||
scroll-height="flex"
|
||||
@focusin="setFavFocus"
|
||||
@focusout="unsetFavFocus"
|
||||
:filters="filters"
|
||||
>
|
||||
<pv-column
|
||||
field="name"
|
||||
expander
|
||||
class="text-break"
|
||||
>
|
||||
<template #header>
|
||||
<div class="text-right">
|
||||
<div class="p-input-icon-left">
|
||||
<i class="pi pi-search"></i>
|
||||
<input
|
||||
type="text"
|
||||
v-model="filters['global']"
|
||||
class="form-control ps-5"
|
||||
placeholder="Search"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #body="{ node }">
|
||||
<span
|
||||
v-if="['semester', 'verband', 'gruppe', 'gruppe_kurzbz'].some(key => node.data.hasOwnProperty(key))"
|
||||
:data-tree-item-key="node.key"
|
||||
:title="node.data.studiengang_kz"
|
||||
v-drag-click="() => toggleTreeNode(node)"
|
||||
v-drop:link-strict.student-collection="(evt, students) => dropStudents(node, students)"
|
||||
>
|
||||
{{ node.data.name }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
:data-tree-item-key="node.key"
|
||||
:title="node.data.studiengang_kz"
|
||||
v-drag-click="() => toggleTreeNode(node)"
|
||||
>
|
||||
{{ node.data.name }}
|
||||
</span>
|
||||
</template>
|
||||
</pv-column>
|
||||
<pv-column
|
||||
field="fav"
|
||||
class="flex-shrink-0 flex-grow-0"
|
||||
header-class="flex-shrink-0 flex-grow-0"
|
||||
>
|
||||
<template #header>
|
||||
<a
|
||||
v-if="favorites.on || favorites.list.length"
|
||||
href="#"
|
||||
@click.prevent="filterFav"
|
||||
>
|
||||
<i
|
||||
:class="favorites.on ? 'fa-solid' : 'fa-regular'"
|
||||
class="fa-star"
|
||||
></i>
|
||||
</a>
|
||||
</template>
|
||||
<template #body="{ node }">
|
||||
<a
|
||||
v-if="node.data.root"
|
||||
href="#"
|
||||
tabindex="-1"
|
||||
data-link-fav-add
|
||||
@click.prevent="markFav(node)"
|
||||
@keydown.enter.stop.prevent="markFav(node)"
|
||||
>
|
||||
<i
|
||||
:class="favorites.list.includes(node.data.link + '') ? 'fa-solid' : 'fa-regular'"
|
||||
class="fa-star"
|
||||
></i>
|
||||
</a>
|
||||
</template>
|
||||
</pv-column>
|
||||
<pv-column field="studiengang_kz" class="d-none"></pv-column>
|
||||
</pv-treetable>
|
||||
<base-treemenu
|
||||
ref="menu"
|
||||
config="stv"
|
||||
:preselected-key="preselectedKey"
|
||||
@select-entry="onSelectTreeNode"
|
||||
@drop="onDrop"
|
||||
/>
|
||||
</div>`
|
||||
};
|
||||
|
||||
@@ -219,7 +219,7 @@ export default {
|
||||
:scrollable="true"
|
||||
:lazy="true"
|
||||
:activeIndex="calcActiveIndex"
|
||||
:pt="{navContainer:{style: 'flex: 0 0 auto;'}, panelContainer:{class: 'overflow-y-scroll p-2', style: 'flex: 1 1 auto;'}}"
|
||||
:pt="{navContainer:{style: 'flex: 0 0 auto;'}, panelContainer:{class: 'overflow-y-scroll p-2 pe-3', style: 'flex: 1 1 auto;'}}"
|
||||
@tab-click="handleTabClick"
|
||||
>
|
||||
<tabpanel
|
||||
|
||||
@@ -3,6 +3,7 @@ import FormInput from "../Form/Input.js";
|
||||
import BsModal from '../Bootstrap/Modal.js';
|
||||
|
||||
export default {
|
||||
name: 'TagComponent',
|
||||
components: {
|
||||
CoreForm,
|
||||
FormInput,
|
||||
@@ -27,6 +28,10 @@ export default {
|
||||
confirmLimit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
showHover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -44,7 +49,10 @@ export default {
|
||||
insertvon: "",
|
||||
updateamum: "",
|
||||
updatevon: "",
|
||||
response: ""
|
||||
response: "",
|
||||
start: "",
|
||||
ende: "",
|
||||
prioritaet: 100,
|
||||
},
|
||||
mode: "create"
|
||||
};
|
||||
@@ -87,7 +95,12 @@ export default {
|
||||
this.tagData.bearbeiter = item.bearbeiter;
|
||||
this.tagData.verfasser = item.verfasser;
|
||||
this.tagData.readonly = item.readonly;
|
||||
|
||||
//add for automated tags
|
||||
this.tagData.automatisiert = item.automatisiert;
|
||||
this.tagData.start = this.formatDateTimeDay(item.start);
|
||||
this.tagData.ende = this.formatDateTimeDay(item.ende);
|
||||
this.tagData.prioritaet = item.prioritaet || 100;
|
||||
|
||||
if (item && item.notiz_id)
|
||||
{
|
||||
this.selectedTagId = item.notiz_id;
|
||||
@@ -185,7 +198,8 @@ export default {
|
||||
updateamum: "",
|
||||
bearbeiter: "",
|
||||
response: "",
|
||||
readonly: false
|
||||
readonly: false,
|
||||
prioritaet: 100,
|
||||
};
|
||||
this.selectedTagId = null;
|
||||
this.mode = "create";
|
||||
@@ -201,6 +215,14 @@ export default {
|
||||
second: "2-digit"
|
||||
});
|
||||
},
|
||||
formatDateTimeDay: (dateString) => {
|
||||
if (!dateString) return null;
|
||||
return new Date(dateString).toLocaleString('de-AT', {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
},
|
||||
async copy (){
|
||||
await navigator.clipboard.writeText(this.tagData.notiz);
|
||||
}
|
||||
@@ -210,7 +232,8 @@ export default {
|
||||
<span :title="values.length === 0 ? 'Bitte Zeilen markieren' : ''">
|
||||
<button @mouseover="showList = true"
|
||||
:disabled="!values || values.length === 0"
|
||||
class="btn btn-sm">
|
||||
class="btn btn-sm"
|
||||
:class="{'btn-hover': showHover}">
|
||||
<i class="fa-solid fa-tag fa-xl"></i>
|
||||
</button>
|
||||
</span>
|
||||
@@ -258,6 +281,15 @@ export default {
|
||||
<span v-if="tagData.bearbeiter && tagData.insertamum !== tagData.updateamum">
|
||||
{{ $p.t('notiz', 'tag_bearbeiter', { 0: tagData.bearbeiter, 1: tagData.updateamum }) }}
|
||||
</span>
|
||||
<span v-if="tagData.start || tagData.ende" >
|
||||
gültig
|
||||
</span>
|
||||
<span v-if="tagData.start">
|
||||
von {{tagData.start}}
|
||||
</span>
|
||||
<span v-if="tagData.ende">
|
||||
bis {{tagData.ende}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
export function idTagFormatter (id, tagData, tagComponent, typeId, semesterStart=null, semesterEnd=null)
|
||||
{
|
||||
if (!id) return;
|
||||
|
||||
const hasSemesterFilter = !!(semesterStart && semesterEnd);
|
||||
|
||||
const semStart = hasSemesterFilter ? new Date(semesterStart) : null;
|
||||
const semEnd = hasSemesterFilter ? new Date(semesterEnd) : null;
|
||||
|
||||
const parsedTags = tagData.map(tag => ({
|
||||
id: tag.notiz_id,
|
||||
typ_kurzbz: tag.titel?.toLowerCase(),
|
||||
beschreibung: tag.bezeichnung,
|
||||
notiz: tag.text || "",
|
||||
style: tag.style,
|
||||
done: tag.done,
|
||||
automatisiert: tag.automatisiert,
|
||||
typeId: id,
|
||||
validFrom: tag.start ? new Date(tag.start) : null,
|
||||
validTo: tag.ende ? new Date(tag.ende) : null
|
||||
}));
|
||||
|
||||
const isInSemester = (tag) => {
|
||||
if (!hasSemesterFilter) return true;
|
||||
|
||||
if (!tag.validFrom && !tag.validTo) return true;
|
||||
|
||||
if (!tag.validFrom && !tag.validTo) return true;
|
||||
|
||||
if (tag.validFrom && tag.validTo) {
|
||||
return tag.validFrom <= semEnd && tag.validTo >= semStart;
|
||||
}
|
||||
|
||||
if (tag.validFrom && !tag.validTo) {
|
||||
return tag.validFrom <= semEnd;
|
||||
}
|
||||
|
||||
if (!tag.validFrom && tag.validTo) {
|
||||
return tag.validTo >= semStart;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
let container = document.createElement('div');
|
||||
container.className = "d-flex gap-1";
|
||||
|
||||
let maxVisibleTags = 5;
|
||||
let expanded = false;
|
||||
const renderTags = () => {
|
||||
container.innerHTML = '';
|
||||
|
||||
let filtered = parsedTags
|
||||
.filter(t => t != null)
|
||||
.filter(isInSemester);
|
||||
|
||||
filtered.sort((a, b) => {
|
||||
let adone = a.done ? 1 : 0;
|
||||
let bdone = b.done ? 1 : 0;
|
||||
|
||||
if (adone !== bdone) return adone - bdone;
|
||||
return a.prioritaet - b.prioritaet;
|
||||
});
|
||||
|
||||
const tagsToShow = expanded
|
||||
? filtered
|
||||
: filtered.slice(0, maxVisibleTags);
|
||||
|
||||
tagsToShow.forEach(tag => {
|
||||
let tagElement = document.createElement('span');
|
||||
|
||||
if(tag.automatisiert)
|
||||
tagElement.innerHTML = "<i class='fa-solid fa-lock'></i> " + tag.beschreibung;
|
||||
else
|
||||
tagElement.innerText = tag.beschreibung;
|
||||
tagElement.title = tag.notiz;
|
||||
tagElement.className = "tag " + tag.style;
|
||||
|
||||
if (tag.done) {
|
||||
tagElement.className += " tag_done";
|
||||
}
|
||||
if (tag.automatisiert)
|
||||
tagElement.className += " tag_auto";
|
||||
|
||||
tagElement.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
tagComponent.editTag(tag.id);
|
||||
});
|
||||
|
||||
container.appendChild(tagElement);
|
||||
});
|
||||
|
||||
if (filtered.length > maxVisibleTags) {
|
||||
let toggle = document.createElement('button');
|
||||
toggle.innerText = (expanded ? '- ' : '+ ') + (filtered.length - maxVisibleTags);
|
||||
toggle.className = "display_all";
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
expanded = !expanded;
|
||||
renderTags();
|
||||
});
|
||||
|
||||
container.appendChild(toggle);
|
||||
}
|
||||
};
|
||||
|
||||
renderTags();
|
||||
|
||||
return container;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import FormInput from '../Form/Input.js';
|
||||
import ApiUdf from '../../api/udf.js';
|
||||
|
||||
export default {
|
||||
name: 'CoreUdfCmpt',
|
||||
components: {
|
||||
CoreFetchCmpt,
|
||||
FormInput
|
||||
@@ -26,7 +27,8 @@ export default {
|
||||
// The values as associative array
|
||||
modelValue: Object,
|
||||
// Show only fields with a name that exists in the filter
|
||||
filter: [String, Array]
|
||||
filter: [String, Array],
|
||||
readonly: true
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -35,6 +37,13 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPkValid() {
|
||||
// If the provided pk is _not_ valid
|
||||
return !Object.keys(this.pk).every(key => !this.pk[key]);
|
||||
},
|
||||
getReadonly() {
|
||||
return this.readonly;
|
||||
},
|
||||
filterArray() {
|
||||
if (!this.filter || Array.isArray(this.filter))
|
||||
return this.filter;
|
||||
@@ -61,6 +70,10 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
pk(n, o) {
|
||||
|
||||
// If the provided pk is _not_ valid
|
||||
if (Object.keys(n).every(key => !n[key])) return;
|
||||
|
||||
if (!this.$refs.fetch)
|
||||
return; // NOTE(chris): no initial load yet
|
||||
|
||||
@@ -124,6 +137,7 @@ export default {
|
||||
template: `
|
||||
<div class="core-udf row">
|
||||
<core-fetch-cmpt
|
||||
v-if="isPkValid"
|
||||
ref="fetch"
|
||||
:api-function="loadF"
|
||||
:api-function-parameters="{ ciModel, pk }"
|
||||
@@ -138,7 +152,7 @@ export default {
|
||||
:type="field.type"
|
||||
:multiple="field.multiple"
|
||||
:title="field.description"
|
||||
:disabled="field.disabled"
|
||||
:disabled="field.disabled || getReadonly"
|
||||
:clearable="field.clearable"
|
||||
:auto-apply="field.autoApply"
|
||||
:enable-time-picker="field.enableTimePicker"
|
||||
@@ -152,4 +166,4 @@ export default {
|
||||
</template>
|
||||
</core-fetch-cmpt>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* Copyright (C) 2026 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/>.
|
||||
*/
|
||||
|
||||
import ApiTabulatorPresets from "../../../api/factory/tabulatorPresets.js";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
name: "TablePresets",
|
||||
props: {
|
||||
presetsId: { type: String },
|
||||
tabulator: { type: Object },
|
||||
generalPresets: { type: Array, default: [] },
|
||||
},
|
||||
emits: ["tablePresetApplied"],
|
||||
data: function () {
|
||||
return {
|
||||
customUserPresets: [],
|
||||
presetInfo: null,
|
||||
newPreset: null,
|
||||
newPresetName: "",
|
||||
presetIndexBeingApplied: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allPresets() {
|
||||
return this.$props.generalPresets.concat(this.customUserPresets);
|
||||
},
|
||||
presetInfoFormattedStrings() {
|
||||
if (!this.presetInfo) return null;
|
||||
|
||||
const columnTitles = this.getColumnTitles();
|
||||
|
||||
let displayedColumns =
|
||||
this.presetInfo.displayedColumns?.map(
|
||||
(columnField) => columnTitles[columnField] ?? "",
|
||||
) ?? [];
|
||||
displayedColumns = displayedColumns.filter(
|
||||
(string) => string.length,
|
||||
);
|
||||
const displayedColumnsString = displayedColumns.length
|
||||
? this.$p.t("tabulator_presets/displayed_columns") +
|
||||
" (" +
|
||||
this.$p.t("tabulator_presets/in_order") +
|
||||
"): " +
|
||||
displayedColumns.join(", ")
|
||||
: "";
|
||||
|
||||
let headerFilters = this.presetInfo.headerFilters
|
||||
? Object.keys(this.presetInfo.headerFilters).map((column) =>
|
||||
columnTitles[column]
|
||||
? columnTitles[column] +
|
||||
'["' +
|
||||
this.presetInfo.headerFilters[column] +
|
||||
'"]'
|
||||
: "",
|
||||
)
|
||||
: [];
|
||||
headerFilters = headerFilters.filter((string) => string.length);
|
||||
const headerFiltersString = headerFilters.length
|
||||
? this.$p.t("tabulator_presets/column_header_filters") +
|
||||
": " +
|
||||
headerFilters.join(", ")
|
||||
: "";
|
||||
|
||||
const sortColumn = this.presetInfo.sort?.column
|
||||
? columnTitles[this.presetInfo.sort.column]
|
||||
: null;
|
||||
const sortDirection =
|
||||
this.presetInfo.sort?.direction === "desc"
|
||||
? this.$p.t("tabulator_presets/desc")
|
||||
: this.$p.t("tabulator_presets/asc");
|
||||
const sortString = sortColumn
|
||||
? this.$p.t("tabulator_presets/sort") +
|
||||
": " +
|
||||
sortColumn +
|
||||
" (" +
|
||||
sortDirection +
|
||||
")"
|
||||
: "";
|
||||
|
||||
return {
|
||||
displayedColumns: displayedColumnsString,
|
||||
headerFilters: headerFiltersString,
|
||||
sort: sortString,
|
||||
};
|
||||
},
|
||||
newPresetFormattedStrings() {
|
||||
if (!this.newPreset) return null;
|
||||
|
||||
const columnTitles = this.getColumnTitles();
|
||||
|
||||
const displayedColumns = this.newPreset.displayedColumns.map(
|
||||
(columnField) => columnTitles[columnField],
|
||||
);
|
||||
const displayedColumnsString = displayedColumns.length
|
||||
? this.$p.t("tabulator_presets/displayed_columns") +
|
||||
" (" +
|
||||
this.$p.t("tabulator_presets/in_order") +
|
||||
"): " +
|
||||
displayedColumns.join(", ")
|
||||
: "";
|
||||
|
||||
const headerFilters = Object.keys(this.newPreset.headerFilters).map(
|
||||
(column) =>
|
||||
columnTitles[column] +
|
||||
'["' +
|
||||
this.newPreset.headerFilters[column] +
|
||||
'"]',
|
||||
);
|
||||
const headerFiltersString = headerFilters.length
|
||||
? this.$p.t("tabulator_presets/column_header_filters") +
|
||||
": " +
|
||||
headerFilters.join(", ")
|
||||
: "";
|
||||
|
||||
const sortColumn = this.newPreset.sort?.column
|
||||
? columnTitles[this.newPreset.sort.column]
|
||||
: null;
|
||||
const sortDirection =
|
||||
this.newPreset.sort?.direction === "desc"
|
||||
? this.$p.t("tabulator_presets/desc")
|
||||
: this.$p.t("tabulator_presets/asc");
|
||||
const sortString = sortColumn
|
||||
? this.$p.t("tabulator_presets/sort") +
|
||||
": " +
|
||||
sortColumn +
|
||||
" (" +
|
||||
sortDirection +
|
||||
")"
|
||||
: "";
|
||||
|
||||
return {
|
||||
displayedColumns: displayedColumnsString,
|
||||
headerFilters: headerFiltersString,
|
||||
sort: sortString,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
allPresets: {
|
||||
handler() {
|
||||
this.hidePresetInfo();
|
||||
this.hideNewPresetForm();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPresetIndex() {
|
||||
return this.presetIndexBeingApplied;
|
||||
},
|
||||
async showPresetInfo(preset) {
|
||||
let shouldUpdatePresetInfo;
|
||||
if (!this.presetInfo) {
|
||||
shouldUpdatePresetInfo = true;
|
||||
} else if (preset.id) {
|
||||
shouldUpdatePresetInfo = preset.id !== this.presetInfo.id;
|
||||
} else {
|
||||
shouldUpdatePresetInfo = preset.name !== this.presetInfo.name;
|
||||
}
|
||||
|
||||
if (shouldUpdatePresetInfo) {
|
||||
this.presetInfo = { ...preset };
|
||||
}
|
||||
|
||||
this.hideNewPresetForm();
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
if (
|
||||
!this.$refs.presetInfoCollapsible
|
||||
.getAttribute("class")
|
||||
.split(" ")
|
||||
.includes("show")
|
||||
) {
|
||||
this.togglePresetInfoCollapsible();
|
||||
}
|
||||
},
|
||||
async hidePresetInfo() {
|
||||
if (
|
||||
this.$refs.presetInfoCollapsible
|
||||
.getAttribute("class")
|
||||
.split(" ")
|
||||
.includes("show")
|
||||
) {
|
||||
this.togglePresetInfoCollapsible();
|
||||
}
|
||||
await this.$nextTick();
|
||||
this.presetInfo = null;
|
||||
},
|
||||
togglePresetInfoCollapsible() {
|
||||
new bootstrap.Collapse(
|
||||
"#presetInfoCollapsible_" + this.$props.presetsId,
|
||||
{ toggle: true },
|
||||
);
|
||||
},
|
||||
async showNewPresetForm() {
|
||||
this.generateNewPreset();
|
||||
|
||||
this.hidePresetInfo();
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
if (
|
||||
!this.$refs.newPresetFormCollapsible
|
||||
.getAttribute("class")
|
||||
.split(" ")
|
||||
.includes("show")
|
||||
) {
|
||||
this.toggleNewPresetFormCollapsible();
|
||||
}
|
||||
},
|
||||
generateNewPreset() {
|
||||
const columns = this.tabulator.columnManager.columns.filter(
|
||||
(column) => column.field !== "collapse",
|
||||
);
|
||||
|
||||
const displayedColumns = columns
|
||||
.filter((column) => column.visible)
|
||||
.map((column) => column.field);
|
||||
|
||||
const unparsedHeaderFilters =
|
||||
this.tabulator.modules.filter.headerFilters;
|
||||
const headerFilterFieldValuePairs = Object.entries(
|
||||
unparsedHeaderFilters,
|
||||
).map(([columnField, filter]) => [columnField, filter.value]);
|
||||
const headerFiltersFieldValuePairsExcludingHiddenColumns =
|
||||
headerFilterFieldValuePairs.filter(
|
||||
([columnField, filterValue]) => {
|
||||
return displayedColumns.includes(columnField);
|
||||
},
|
||||
);
|
||||
const headerFilters = Object.fromEntries(
|
||||
headerFiltersFieldValuePairsExcludingHiddenColumns,
|
||||
);
|
||||
|
||||
const activeSort = this.tabulator.getSorters()[0];
|
||||
const sort = activeSort
|
||||
? {
|
||||
column: activeSort.field,
|
||||
direction: activeSort.dir,
|
||||
}
|
||||
: null;
|
||||
|
||||
this.newPreset = {
|
||||
displayedColumns,
|
||||
headerFilters,
|
||||
sort,
|
||||
};
|
||||
},
|
||||
hideNewPresetForm() {
|
||||
if (
|
||||
this.$refs.newPresetFormCollapsible
|
||||
.getAttribute("class")
|
||||
.split(" ")
|
||||
.includes("show")
|
||||
) {
|
||||
this.toggleNewPresetFormCollapsible();
|
||||
}
|
||||
},
|
||||
toggleNewPresetFormCollapsible() {
|
||||
this.newPresetName = "";
|
||||
new bootstrap.Collapse(
|
||||
"#newPresetFormCollapsible_" + this.$props.presetsId,
|
||||
{ toggle: true },
|
||||
);
|
||||
},
|
||||
async createPreset() {
|
||||
if (!this.newPresetName?.length) return;
|
||||
|
||||
const presetCreationResponse = await this.$api.call(
|
||||
ApiTabulatorPresets.createTabulatorPreset(
|
||||
this.$props.presetsId,
|
||||
this.newPresetName,
|
||||
this.newPreset,
|
||||
),
|
||||
);
|
||||
|
||||
if (presetCreationResponse.meta.status === "success") {
|
||||
this.fetchCustomUserTabulatorPresets();
|
||||
this.hideNewPresetForm();
|
||||
}
|
||||
},
|
||||
async deletePreset(preset) {
|
||||
if (!preset.id) return;
|
||||
|
||||
if (
|
||||
window.confirm(
|
||||
this.$p
|
||||
.t("tabulator_presets/preset_deletion_confirmation")
|
||||
.replace("(((presetName)))", preset.name),
|
||||
)
|
||||
) {
|
||||
const presetDeletionResponse = await this.$api.call(
|
||||
ApiTabulatorPresets.deleteTabulatorPreset(preset.id),
|
||||
);
|
||||
|
||||
if (presetDeletionResponse.meta.status === "success") {
|
||||
this.fetchCustomUserTabulatorPresets();
|
||||
}
|
||||
}
|
||||
},
|
||||
syncPresetWithConfig(preset) {
|
||||
const validColumns = this.$props.tabulator.getColumnDefinitions();
|
||||
let isUpdateNecessary = false;
|
||||
|
||||
if (preset.displayedColumns) {
|
||||
const initialNumberOfDisplayedColumns =
|
||||
preset.displayedColumns.length;
|
||||
preset.displayedColumns = preset.displayedColumns.filter(
|
||||
(columnField) => {
|
||||
return validColumns.some(
|
||||
(column) => column.field === columnField,
|
||||
);
|
||||
},
|
||||
);
|
||||
const updatedNumberOfDisplayedColumns =
|
||||
preset.displayedColumns.length;
|
||||
|
||||
isUpdateNecessary =
|
||||
updatedNumberOfDisplayedColumns <
|
||||
initialNumberOfDisplayedColumns;
|
||||
}
|
||||
|
||||
if (preset.headerFilters) {
|
||||
const initialNumberOfHeaderFilters = Object.keys(
|
||||
preset.headerFilters,
|
||||
).length;
|
||||
Object.keys(preset.headerFilters).forEach((columnField) => {
|
||||
if (
|
||||
!validColumns.some(
|
||||
(column) =>
|
||||
column.field === columnField &&
|
||||
column.headerFilter,
|
||||
) ||
|
||||
!preset.displayedColumns.includes(columnField)
|
||||
) {
|
||||
delete preset.headerFilters[columnField];
|
||||
}
|
||||
});
|
||||
const updatedNumberOfHeaderFilters = Object.keys(
|
||||
preset.headerFilters,
|
||||
).length;
|
||||
|
||||
isUpdateNecessary =
|
||||
isUpdateNecessary ||
|
||||
updatedNumberOfHeaderFilters < initialNumberOfHeaderFilters;
|
||||
}
|
||||
|
||||
if (preset.sort) {
|
||||
if (
|
||||
!validColumns.some(
|
||||
(column) =>
|
||||
column.field === preset.sort.column &&
|
||||
column.headerSort,
|
||||
)
|
||||
) {
|
||||
preset.sort = null;
|
||||
isUpdateNecessary = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUpdateNecessary) {
|
||||
this.updatePreset(preset);
|
||||
}
|
||||
|
||||
return preset;
|
||||
},
|
||||
async updatePreset(preset) {
|
||||
if (!preset.id) return;
|
||||
|
||||
const tabulatorPresetUpdateResponse = await this.$api.call(
|
||||
ApiTabulatorPresets.updateTabulatorPreset(preset.id, {
|
||||
displayedColumns: preset.displayedColumns,
|
||||
headerFilters: preset.headerFilters,
|
||||
sort: preset.sort,
|
||||
}),
|
||||
);
|
||||
|
||||
if (tabulatorPresetUpdateResponse.meta.status === "success") {
|
||||
this.fetchCustomUserTabulatorPresets();
|
||||
}
|
||||
},
|
||||
async applyPreset(preset, index) {
|
||||
this.presetIndexBeingApplied = index;
|
||||
|
||||
// workaround for updating DOM and displaying preset-loading spinner
|
||||
// conventional use of $nextTick ineffective
|
||||
setTimeout(() => {
|
||||
preset = this.syncPresetWithConfig(preset);
|
||||
window.localStorage["tabulatorPreset-" + this.$props.presetsId] =
|
||||
preset.id ? "custom:" + preset.id : "general:" + preset.name;
|
||||
|
||||
this.$props.tabulator.modules.tablePresets.applyPreset(preset);
|
||||
|
||||
this.$emit("tablePresetApplied", { preset });
|
||||
|
||||
this.presetIndexBeingApplied = null;
|
||||
}, 50);
|
||||
},
|
||||
async fetchCustomUserTabulatorPresets() {
|
||||
let tabulatorPresetsResponse = await this.$api.call(
|
||||
ApiTabulatorPresets.getTabulatorPresets(this.$props.presetsId),
|
||||
);
|
||||
|
||||
if (tabulatorPresetsResponse.meta.status === "success") {
|
||||
this.customUserPresets = tabulatorPresetsResponse.data.map(
|
||||
(presetInfo) => {
|
||||
return {
|
||||
id: presetInfo.preset_id,
|
||||
name: presetInfo.preset_name,
|
||||
...JSON.parse(presetInfo.preset_json),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
getColumnTitles() {
|
||||
const columnLocalizations =
|
||||
this.$props.tabulator.getLocale() !== "default"
|
||||
? this.$props.tabulator.getLang().columns
|
||||
: {};
|
||||
const columnFieldTitlePairs = this.$props.tabulator
|
||||
.getColumnDefinitions()
|
||||
.map((column) => [
|
||||
column.field,
|
||||
columnLocalizations[column.field] ?? column.title,
|
||||
]);
|
||||
return Object.fromEntries(columnFieldTitlePairs);
|
||||
},
|
||||
applyStoredPreset() {
|
||||
let storedPreset =
|
||||
window.localStorage["tabulatorPreset-" + this.$props.presetsId];
|
||||
if (!storedPreset) return;
|
||||
|
||||
let preset = null;
|
||||
|
||||
if (storedPreset.search("custom:") === 0) {
|
||||
const customPresetId = parseInt(storedPreset.slice(7));
|
||||
preset = this.customUserPresets.find(
|
||||
(customPreset) => customPreset.id === customPresetId,
|
||||
);
|
||||
} else if (storedPreset.search("general:") === 0) {
|
||||
const generalPresetName = storedPreset.slice(8);
|
||||
preset = this.$props.generalPresets.find(
|
||||
(generalPreset) => generalPreset.name === generalPresetName,
|
||||
);
|
||||
}
|
||||
|
||||
if (!preset) return;
|
||||
|
||||
this.applyPreset(preset, null);
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.$p.loadCategory(["tabulator_presets", "global"]);
|
||||
await this.fetchCustomUserTabulatorPresets();
|
||||
this.applyStoredPreset();
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-header">{{ $p.t("tabulator_presets/table_presets") }}</div>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex flex-row gap-1 justify-content-start flex-wrap">
|
||||
<div v-for="(preset, index) in allPresets" class="d-flex flex-column gap-1 mb-2">
|
||||
<div @click="applyPreset(preset, index)" class="position-relative btn btn-dark py-1 px-2">
|
||||
<span :class="{'opacity-0': getPresetIndex() === index}">{{ preset.name }}</span>
|
||||
<div
|
||||
v-if="getPresetIndex() === index"
|
||||
class="position-absolute d-flex flex-row align-items-center justify-content-center"
|
||||
style="inset:0;"
|
||||
>
|
||||
<div class="spinner-border spinner-border-sm" role="status" style>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row justify-content-center">
|
||||
<div class="d-flex flex-row gap-2 px-3">
|
||||
<span @click="showPresetInfo(preset)" type="button" class="fa-solid fa-circle-info"></span>
|
||||
<span v-if="preset.id" @click="deletePreset(preset)" type="button" class="fa-solid fa-trash-can"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div @click="showNewPresetForm()" class="btn btn-dark py-1 px-2 d-flex flex-row align-items-center gap-1">
|
||||
<span class="fa-solid fa-plus"></span>
|
||||
<span class="flex-nowrap">{{ $p.t("tabulator_presets/save_current_config") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :id="'presetInfoCollapsible_' + $props.presetsId" ref="presetInfoCollapsible" class="collapse">
|
||||
<div class="d-flex flex-column">
|
||||
<hr />
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<div class="w-100 d-flex flex-row justify-content-between">
|
||||
<span class="fw-bold">{{ presetInfo?.name }}</span>
|
||||
<span @click="hidePresetInfo()" type="button" class="fa-solid fa-xmark"></span>
|
||||
</div>
|
||||
<span v-if="presetInfoFormattedStrings?.displayedColumns.length">{{ presetInfoFormattedStrings.displayedColumns }}</span>
|
||||
<span v-if="presetInfoFormattedStrings?.headerFilters.length">{{ presetInfoFormattedStrings.headerFilters }}</span>
|
||||
<span v-if="presetInfoFormattedStrings?.sort.length">{{ presetInfoFormattedStrings.sort }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :id="'newPresetFormCollapsible_' + $props.presetsId" ref="newPresetFormCollapsible" class="collapse">
|
||||
<div class="d-flex flex-column">
|
||||
<hr />
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<div class="w-100 d-flex flex-row justify-content-between">
|
||||
<span class="fw-bold">{{ $p.t("tabulator_presets/save_current_config") }}</span>
|
||||
<span @click="hideNewPresetForm()" type="button" class="fa-solid fa-xmark"></span>
|
||||
</div>
|
||||
<span v-if="newPresetFormattedStrings?.displayedColumns.length">{{ newPresetFormattedStrings.displayedColumns }}</span>
|
||||
<span v-if="newPresetFormattedStrings?.headerFilters.length">{{ newPresetFormattedStrings.headerFilters }}</span>
|
||||
<span v-if="newPresetFormattedStrings?.sort.length">{{ newPresetFormattedStrings.sort }}</span>
|
||||
<div class="d-flex flex-row justify-content-center align-items-center gap-2">
|
||||
<input v-model="newPresetName" :placeholder="$p.t('tabulator_presets/preset_name')" />
|
||||
<div
|
||||
@click="createPreset()"
|
||||
:class="{'opacity-50 pe-none': !newPresetName?.length}"
|
||||
class="btn btn-dark py-1 px-2"
|
||||
>
|
||||
{{ $p.t('global/speichern') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -202,7 +202,7 @@ export default {
|
||||
return true;
|
||||
},
|
||||
download(config) {
|
||||
this.tabulator.download(config.formatter, config.file, config.options)
|
||||
this.tabulator.download(config.formatter, config.file, config.options, config.rowRange)
|
||||
}
|
||||
},
|
||||
template: `
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
export default {
|
||||
name: 'HorizontalSplit',
|
||||
props: {
|
||||
defaultRatio: {
|
||||
type: Array,
|
||||
default: () => [50, 50]
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
availWidth: 0,
|
||||
leftwidth: 0,
|
||||
rightwidth: 0,
|
||||
mousePosX: 0,
|
||||
resize: false,
|
||||
hsplitterOffset: 0,
|
||||
selfOffsetLeft: 0
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div ref="horizontalsplit" class="horizontalsplit-container">
|
||||
<div ref="leftpanel" class="horizontalsplitted"
|
||||
:style="{ width: this.leftwidthcss }">
|
||||
<slot name="left">
|
||||
<p>Left Panel</p>
|
||||
</slot>
|
||||
</div>
|
||||
<div ref="hsplitter" class="horizontalsplitter"
|
||||
:class="this.leftOrRightClass" @mousedown="this.dragStart">
|
||||
<div class="splitactions horizontal" :class="this.leftOrRightClass">
|
||||
<span @click="this.collapseRight" class="splitaction">
|
||||
<i class="fas fa-angle-right text-muted"></i>
|
||||
</span>
|
||||
<span @dblclick="this.showBoth" class="splitaction resize">
|
||||
<i class="fas fa-grip-lines-vertical text-muted"></i>
|
||||
</span>
|
||||
<span @click="this.collapseLeft" class="splitaction">
|
||||
<i class="fas fa-angle-left text-muted"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="rightpanel" class="horizontalsplitted"
|
||||
:style="{ width: this.rightwidthcss }">
|
||||
<slot name="right">
|
||||
<p/>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
mounted: function () {
|
||||
this.calcWidths();
|
||||
this.trackHorizontalSplitterOffsetLeft();
|
||||
window.addEventListener('resize', this.calcWidths);
|
||||
},
|
||||
updated: function () {
|
||||
this.trackHorizontalSplitterOffsetLeft();
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
window.removeEventListener('resize', this.calcWidths);
|
||||
},
|
||||
methods: {
|
||||
calcWidths: function () {
|
||||
var oldavailWidth = this.availWidth;
|
||||
this.selfOffsetLeft = this.$refs.horizontalsplit.offsetLeft;
|
||||
this.availWidth = this.$refs.horizontalsplit.offsetWidth - this.$refs.hsplitter.offsetWidth;
|
||||
|
||||
if ((this.leftwidth === 0 && this.rightwidth === 0) || oldavailWidth === 0) {
|
||||
this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100));
|
||||
} else {
|
||||
this.leftwidth = Math.floor(((this.leftwidth * 100) / oldavailWidth) / 100 * this.availWidth);
|
||||
}
|
||||
this.rightwidth = this.availWidth - this.leftwidth;
|
||||
},
|
||||
collapseLeft: function () {
|
||||
this.calcWidths();
|
||||
this.leftwidth = 0;
|
||||
this.rightwidth = this.availWidth;
|
||||
},
|
||||
collapseRight: function () {
|
||||
this.calcWidths();
|
||||
this.leftwidth = this.availWidth;
|
||||
this.rightwidth = 0;
|
||||
},
|
||||
showBoth: function () {
|
||||
this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100));
|
||||
this.rightwidth = this.availWidth - this.leftwidth;
|
||||
},
|
||||
isCollapsed: function () {
|
||||
if (this.leftwidth === 0) {
|
||||
return 'left';
|
||||
} else if (this.rightwidth === 0) {
|
||||
return 'right';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
dragStart: function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.addEventListener('mouseup', this.dragEnd);
|
||||
window.addEventListener('mousemove', this.drag);
|
||||
this.resize = true;
|
||||
this.mousePosX = e.clientX;
|
||||
},
|
||||
drag: function (e) {
|
||||
if (!this.resize) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var offsetX = e.clientX - this.mousePosX;
|
||||
this.leftwidth = this.leftwidth + offsetX;
|
||||
if (this.leftwidth < 0) {
|
||||
this.leftwidth = 0;
|
||||
}
|
||||
if (this.leftwidth > this.availWidth) {
|
||||
this.leftwidth = this.availWidth;
|
||||
}
|
||||
this.rightwidth = this.availWidth - this.leftwidth;
|
||||
this.mousePosX = e.clientX;
|
||||
},
|
||||
dragEnd: function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.removeEventListener('mousemove', this.drag);
|
||||
window.removeEventListener('mouseup', this.dragEnd);
|
||||
this.resize = false;
|
||||
this.mousePosX = e.clientX;
|
||||
},
|
||||
trackHorizontalSplitterOffsetLeft: function () {
|
||||
this.hsplitterOffset = this.$refs.hsplitter.offsetLeft;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
leftOrRightClass: function () {
|
||||
return ((this.hsplitterOffset - this.selfOffsetLeft) <= Math.floor(this.availWidth / 2))
|
||||
? 'left'
|
||||
: 'right';
|
||||
},
|
||||
leftwidthcss: function () {
|
||||
return this.leftwidth + 'px';
|
||||
},
|
||||
rightwidthcss: function () {
|
||||
return this.rightwidth + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -40,7 +40,7 @@ export default {
|
||||
query: Vue.computed(() => this.lastQuery),
|
||||
};
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
inject: { isMobile: { default: false } },
|
||||
data: function () {
|
||||
return {
|
||||
searchtimer: null,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
export default {
|
||||
name: 'VerticalSplit',
|
||||
props: {
|
||||
defaultRatio: {
|
||||
type: Array,
|
||||
default: () => [50, 50]
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
availHeight: 0,
|
||||
@@ -50,17 +56,22 @@ export default {
|
||||
updated: function() {
|
||||
this.trackVerticalSplitterOffsetTop();
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
window.removeEventListener('resize', this.calcHeights);
|
||||
},
|
||||
methods: {
|
||||
calcHeights: function() {
|
||||
var windowheight = window.innerHeight;
|
||||
var oldavailHeight = this.availHeight;
|
||||
this.selfOffsetTop = this.$refs.verticalsplit.offsetTop;
|
||||
this.availHeight = windowheight - this.selfOffsetTop - this.$refs.vsplitter.offsetHeight;
|
||||
|
||||
if( (this.topheight === 0 && this.bottomheight === 0) || oldavailHeight === 0 ) {
|
||||
this.topheight = Math.floor(this.availHeight/2);
|
||||
this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100));
|
||||
} else {
|
||||
this.topheight = Math.floor( ((((this.topheight * 100) / oldavailHeight) / 100) * this.availHeight) );
|
||||
}
|
||||
|
||||
this.bottomheight = this.availHeight - this.topheight;
|
||||
},
|
||||
collapseTop: function() {
|
||||
@@ -74,8 +85,8 @@ export default {
|
||||
this.bottomheight = 0;
|
||||
},
|
||||
showBoth: function() {
|
||||
this.topheight = Math.floor(this.availHeight/2);
|
||||
this.bottomheight = Math.floor(this.availHeight/2);
|
||||
this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100));
|
||||
this.bottomheight = this.availHeight - this.topheight
|
||||
},
|
||||
isCollapsed: function() {
|
||||
if( this.topheight === 0 ) {
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
// i.e: for dragging multiple elements
|
||||
// otherwise set draggable attribute
|
||||
if (!binding.modifiers.capture) {
|
||||
el.draggable = true;
|
||||
updateAttributes(el);
|
||||
}
|
||||
|
||||
const bcc = new BroadcastChannel('fhc-dnd');
|
||||
@@ -78,11 +78,21 @@ export default {
|
||||
el.fhcDraggableCleanup = () => {
|
||||
el.removeEventListener('dragstart', onStart, binding.modifiers.capture);
|
||||
el.removeEventListener('dragend', onEnd, true);
|
||||
if (el.dataset.fhcDraggableValue) {
|
||||
delete el.dataset.fhcDraggableValue;
|
||||
}
|
||||
if (el.dataset.fhcEffectAllowed) {
|
||||
delete el.dataset.fhcEffectAllowed;
|
||||
}
|
||||
updateAttributes(el);
|
||||
};
|
||||
},
|
||||
updated(el, binding) {
|
||||
updateValue(el, binding.value);
|
||||
updateEffectAllowed(el, binding.arg);
|
||||
if (!binding.modifiers.capture) {
|
||||
updateAttributes(el);
|
||||
}
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
el.fhcDraggableCleanup();
|
||||
@@ -92,7 +102,8 @@ export default {
|
||||
|
||||
// Helper functions
|
||||
function updateValue(el, value) {
|
||||
value = convertToValidDragObject(value);
|
||||
if (value)
|
||||
value = convertToValidDragObject(value);
|
||||
if (value) {
|
||||
el.dataset.fhcDraggableValue = JSON.stringify(value);
|
||||
} else if (el.dataset.fhcDraggableValue) {
|
||||
@@ -106,3 +117,9 @@ function updateEffectAllowed(el, effectAllowed) {
|
||||
delete el.dataset.fhcEffectAllowed;
|
||||
}
|
||||
}
|
||||
function updateAttributes(el) {
|
||||
if (el.dataset.fhcEffectAllowed && el.dataset.fhcDraggableValue)
|
||||
el.draggable = true;
|
||||
else if (el.hasAttribute('draggable'))
|
||||
el.removeAttribute('draggable');
|
||||
}
|
||||
|
||||
@@ -16,6 +16,26 @@ const EFFECTS = [
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
if (!binding.arg) {
|
||||
binding.arg = 'none';
|
||||
} else if (typeof binding.arg === 'object' && !Array.isArray(binding.arg)) {
|
||||
// NOTE(chris): allow object as arg and map it to arg and
|
||||
// modifiers to allow dynamic modifiers.
|
||||
if (binding.arg.allowed) {
|
||||
binding.modifiers = binding.arg.allowed.reduce((a, c) => {
|
||||
a[c] = true;
|
||||
return a;
|
||||
}, {});
|
||||
}
|
||||
if (!binding.arg.effect)
|
||||
binding.arg.effect = 'none';
|
||||
|
||||
if (binding.arg.strict)
|
||||
binding.arg = binding.arg.effect + '-strict';
|
||||
else
|
||||
binding.arg = binding.arg.effect;
|
||||
}
|
||||
|
||||
const allowedTypes = Object.keys(binding.modifiers);
|
||||
allowedTypes.forEach(type => {
|
||||
if (type.substr(-11) == '-collection') {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
export function highlightGesamtnote(zeugnisTable, teacherTable)
|
||||
{
|
||||
let zeugnisData = zeugnisTable.getData();
|
||||
|
||||
let studentsWithNote = new Set();
|
||||
|
||||
zeugnisData.forEach(row => {
|
||||
let student = row.uid;
|
||||
let lv = row.lehrveranstaltung_id;
|
||||
let note = row.note;
|
||||
|
||||
if (!student || !lv)
|
||||
return;
|
||||
|
||||
if (note == null || note === "")
|
||||
return;
|
||||
|
||||
let key = `${student}_${lv}`;
|
||||
studentsWithNote.add(key);
|
||||
});
|
||||
|
||||
teacherTable.deselectRow();
|
||||
|
||||
teacherTable.getRows().forEach(row => {
|
||||
let data = row.getData();
|
||||
|
||||
let student = data.student_uid;
|
||||
let lv = data.lehrveranstaltung_id;
|
||||
let note = data.note;
|
||||
|
||||
if (!student || !lv)
|
||||
return;
|
||||
|
||||
if (note == null || note === "")
|
||||
return;
|
||||
|
||||
let key = `${student}_${lv}`;
|
||||
|
||||
if (!studentsWithNote.has(key))
|
||||
{
|
||||
row.select();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -30,6 +30,10 @@ const TYPE_DEFINITION = {
|
||||
prestudent: {
|
||||
id: "prestudent_id",
|
||||
dragIcon: "fa-solid fa-user-graduate text-muted"
|
||||
},
|
||||
verband: {
|
||||
id: "gruppe_id",
|
||||
dragIcon: "fa-solid fa-user-group text-muted"
|
||||
}
|
||||
// TODO: IMPLEMENT OTHER TYPES
|
||||
};
|
||||
@@ -116,12 +120,12 @@ function isValidTransferData(event, allowedTypes, strict) {
|
||||
function getTransferData(event, strict) {
|
||||
const result = [];
|
||||
|
||||
for (const type of event.dataTransfer.types) {
|
||||
if (type.substr(0, 16) != 'application/fhc-') {
|
||||
if (strict)
|
||||
return null;
|
||||
continue;
|
||||
}
|
||||
const dataTypes = [...event.dataTransfer.types].filter(type => type.startsWith('application/fhc-'));
|
||||
|
||||
if (!dataTypes.length)
|
||||
return null;
|
||||
|
||||
for (const type of dataTypes) {
|
||||
let base_type = type.substr(16);
|
||||
let collection = false;
|
||||
if (base_type.substr(-11) == '-collection') {
|
||||
@@ -266,18 +270,16 @@ function eventHasTypes(event, allowedTypes, strict) {
|
||||
allowedTypes = VALID_TYPES;
|
||||
allowedTypes = allowedTypes.map(type => 'application/fhc-' + type);
|
||||
|
||||
const dataTypes = [...event.dataTransfer.types];
|
||||
|
||||
const dataTypes = [...event.dataTransfer.types].filter(type => type.startsWith('application/fhc-'));
|
||||
// NOTE(chris): if dragging across browsers the dataTransfer object is
|
||||
// set to a default one without data. Since we do not support dragging
|
||||
// across browsers (yet) we return false which will disallow dropping.
|
||||
if (!dataTypes.length)
|
||||
return false;
|
||||
|
||||
|
||||
if (!strict)
|
||||
return allowedTypes.some(type => [...event.dataTransfer.types].includes(type));
|
||||
|
||||
return [...event.dataTransfer.types].every(type => allowedTypes.includes(type));
|
||||
return allowedTypes.some(type => dataTypes.includes(type));
|
||||
return dataTypes.every(type => allowedTypes.includes(type));
|
||||
}
|
||||
|
||||
function bindDragEnterLeave(el, onEnter, onLeave) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef) {
|
||||
export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef, uidCC) {
|
||||
await phrasenPluginRef.loadCategory('ui');
|
||||
|
||||
let splititem = ",";
|
||||
let maillist = mails.join(splititem);
|
||||
let useBcc = event?.ctrlKey || event?.metaKey;
|
||||
@@ -46,7 +47,10 @@ export async function splitMailsHelper(mails, event, subject, body, alertPluginR
|
||||
}
|
||||
|
||||
// construct the clean mailLink
|
||||
let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`;
|
||||
let mailadressUid = (uidCC && typeof uidCC === 'string')
|
||||
? uidCC + '@' + FHC_JS_DATA_STORAGE_OBJECT.domain
|
||||
: 'noreply@' + FHC_JS_DATA_STORAGE_OBJECT.domain;
|
||||
let mailLink = useBcc ? `mailto:${mailadressUid}?cc=${mailadressUid}&bcc=${mailto}` : `mailto:${mailto}`;
|
||||
if (queryString) {
|
||||
// If using BCC, the string already has a '?', so append with '&'. Otherwise, start with '?'
|
||||
mailLink += useBcc ? `&${queryString}` : `?${queryString}`;
|
||||
@@ -63,4 +67,79 @@ export async function splitMailsHelper(mails, event, subject, body, alertPluginR
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* just splits the list of mails
|
||||
*
|
||||
* @param mails emailadresses
|
||||
* @param subject subject
|
||||
* @param useBcc useBcc
|
||||
* @returns array of links for splitted mails
|
||||
*/
|
||||
export function splitMailLinks(mails, subject = "", useBcc = false, uidCC= "", maxLength = 2024) {
|
||||
if (!Array.isArray(mails) || mails.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const separator = ",";
|
||||
const encodedSubject = subject ? encodeURIComponent(subject) : "";
|
||||
|
||||
// reserve space for the subject parameter
|
||||
const subjectLength = encodedSubject
|
||||
? `?subject=${encodedSubject}`.length
|
||||
: 0;
|
||||
|
||||
const limit = maxLength - subjectLength;
|
||||
|
||||
const links = [];
|
||||
let currentRecipients = [];
|
||||
|
||||
for (const mail of mails) {
|
||||
if (!mail) continue;
|
||||
|
||||
const testRecipients = [...currentRecipients, mail];
|
||||
const recipientString = testRecipients.join(separator);
|
||||
|
||||
if (recipientString.length > limit && currentRecipients.length > 0) {
|
||||
|
||||
links.push(createMailto(
|
||||
currentRecipients.join(separator),
|
||||
encodedSubject,
|
||||
useBcc,
|
||||
uidCC
|
||||
));
|
||||
|
||||
currentRecipients = [mail];
|
||||
}
|
||||
else {
|
||||
currentRecipients.push(mail);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRecipients.length > 0) {
|
||||
links.push(createMailto(
|
||||
currentRecipients.join(separator),
|
||||
encodedSubject,
|
||||
useBcc,
|
||||
uidCC
|
||||
));
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
function createMailto(recipients, encodedSubject, useBcc, uidCC) {
|
||||
let mailadressUid = uidCC + '@technikum-wien.at';
|
||||
|
||||
let link = useBcc
|
||||
? `mailto:${encodeURIComponent(mailadressUid)}?cc=${encodeURIComponent(mailadressUid)}&bcc=${encodeURIComponent(recipients)}`
|
||||
: `mailto:${encodeURIComponent(recipients)}`;
|
||||
|
||||
if (encodedSubject) {
|
||||
link += `${useBcc ? "&" : "?"}subject=${encodedSubject}`;
|
||||
}
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,12 @@ export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags")
|
||||
|
||||
let tags;
|
||||
try {
|
||||
tags = JSON.parse(rowData[tagsKey] || "[]");
|
||||
let initialTags = rowData[tagsKey] || "[]";
|
||||
if (typeof initialTags === 'string') {
|
||||
tags = JSON.parse(initialTags);
|
||||
} else {
|
||||
tags = initialTags;
|
||||
}
|
||||
} catch (e) {
|
||||
tags = [];
|
||||
}
|
||||
@@ -28,8 +33,14 @@ export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags")
|
||||
|
||||
let newTag = { ...addedTag, id: tag.id };
|
||||
|
||||
tags.unshift(newTag);
|
||||
tags.push(newTag);
|
||||
tags.sort((a, b) => {
|
||||
let adone = a.done ? 1 : 0;
|
||||
let bdone = b.done ? 1 : 0;
|
||||
|
||||
if (adone !== bdone) return adone - bdone;
|
||||
return a.prioritaet - b.prioritaet;
|
||||
});
|
||||
rowData[tagsKey] = JSON.stringify(tags);
|
||||
updated = true;
|
||||
});
|
||||
@@ -53,7 +64,12 @@ export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags'])
|
||||
let tags;
|
||||
|
||||
try {
|
||||
tags = JSON.parse(rowData[key] || "[]");
|
||||
let initialTags = rowData[key] || "[]";
|
||||
if (typeof initialTags === 'string') {
|
||||
tags = JSON.parse(initialTags);
|
||||
} else {
|
||||
tags = initialTags;
|
||||
}
|
||||
} catch (e) {
|
||||
tags = [];
|
||||
}
|
||||
@@ -62,7 +78,6 @@ export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags'])
|
||||
return;
|
||||
|
||||
let filtered = tags.filter(tag => tag?.id !== deletedTag);
|
||||
|
||||
if (filtered.length !== tags.length)
|
||||
{
|
||||
updates[key] = JSON.stringify(filtered);
|
||||
@@ -95,7 +110,12 @@ export function updateTagInTable(updatedTag, rows, fields = ['tags'])
|
||||
|
||||
let fieldData;
|
||||
try {
|
||||
fieldData = JSON.parse(rowData[field] || "[]");
|
||||
let initialTags = rowData[field] || "[]";
|
||||
if (typeof initialTags === 'string') {
|
||||
fieldData = JSON.parse(initialTags);
|
||||
} else {
|
||||
fieldData = initialTags;
|
||||
}
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
@@ -108,6 +128,14 @@ export function updateTagInTable(updatedTag, rows, fields = ['tags'])
|
||||
if (index !== -1)
|
||||
{
|
||||
fieldData[index] = { ...updatedTag };
|
||||
fieldData.sort((a, b) => {
|
||||
let adone = a.done ? 1 : 0;
|
||||
let bdone = b.done ? 1 : 0;
|
||||
|
||||
if (adone !== bdone) return adone - bdone;
|
||||
return a.prioritaet - b.prioritaet;
|
||||
});
|
||||
|
||||
let updatedFieldData = JSON.stringify(fieldData);
|
||||
|
||||
if (updatedFieldData !== rowData[field])
|
||||
|
||||
@@ -75,6 +75,7 @@ var Rueckstellung = {
|
||||
if (FHC_AjaxClient.hasData(data))
|
||||
{
|
||||
Rueckstellung.get(personid);
|
||||
InfocenterDetails._refreshMessages();
|
||||
InfocenterDetails._refreshLog()
|
||||
}
|
||||
else
|
||||
|
||||
@@ -9,9 +9,13 @@ export default class ResponsiveLayoutExtensionModule extends ResponsiveLayout {
|
||||
}
|
||||
|
||||
generateCollapsedRowData(row) {
|
||||
let result = super.generateCollapsedRowData(row)
|
||||
const isLocalizationEnabled = this.table.options.locale && this.table.options.locale !== "default";
|
||||
const columnHeadingTranslations = this.table?.getLang()?.columns;
|
||||
let result = super.generateCollapsedRowData(row);
|
||||
const isLocalizationEnabled =
|
||||
this.table.options.locale &&
|
||||
this.table.options.locale !== "default";
|
||||
const columnHeadingTranslations = this.table?.initialized
|
||||
? this.table.getLang()?.columns
|
||||
: null;
|
||||
|
||||
if (isLocalizationEnabled && columnHeadingTranslations) {
|
||||
result = result.map((column) => {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import Module from "../../../../vendor/olifolkerd/tabulator5/src/js/core/Module.js";
|
||||
|
||||
export default class TablePresetsModule extends Module {
|
||||
static moduleName = "tablePresets";
|
||||
static moduleInitNumber = 1;
|
||||
|
||||
constructor(table) {
|
||||
super(table);
|
||||
}
|
||||
|
||||
applyPreset(preset) {
|
||||
let columnsConfig = this.table.options.columns;
|
||||
let reorderedColumnsConfig = [];
|
||||
preset.displayedColumns.forEach((columnField) => {
|
||||
let columnConfig = columnsConfig.find(
|
||||
(column) => column.field === columnField,
|
||||
);
|
||||
if (!columnConfig) return;
|
||||
columnConfig.visible = true;
|
||||
reorderedColumnsConfig.push(columnConfig);
|
||||
});
|
||||
|
||||
let hiddenColumnsConfig = columnsConfig
|
||||
.filter((column) => !preset.displayedColumns.includes(column.field))
|
||||
.map((column) => {
|
||||
column.visible = false;
|
||||
return column;
|
||||
});
|
||||
this.table.setColumns(
|
||||
reorderedColumnsConfig.concat(hiddenColumnsConfig),
|
||||
);
|
||||
|
||||
this.table.clearHeaderFilter();
|
||||
if (preset.headerFilters) {
|
||||
Object.entries(preset.headerFilters).forEach(
|
||||
([columnField, filterValue]) => {
|
||||
this.table.setHeaderFilterValue(columnField, filterValue);
|
||||
|
||||
// double checking if filter inputs are filled in due to issues with custom header filters
|
||||
const headerFilterInputElement =
|
||||
this.table.columnManager.columns
|
||||
.find((column) => column.field === columnField)
|
||||
?.element.querySelector(
|
||||
".tabulator-header-filter input",
|
||||
);
|
||||
if (
|
||||
headerFilterInputElement &&
|
||||
headerFilterInputElement.value !== filterValue
|
||||
) {
|
||||
headerFilterInputElement.value = filterValue;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.table.clearSort();
|
||||
if (preset.sort?.column) {
|
||||
this.table.setSort(
|
||||
preset.sort.column,
|
||||
preset.sort.direction ?? "asc",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
function dateinput(headerValue, rowValue, rowData, config) {
|
||||
if (!luxon)
|
||||
return this.like(headerValue, rowValue, rowData, config);
|
||||
|
||||
let inputFormat = config.inputFormat || 'iso';
|
||||
let outputFormat = config.outputFormat || 'dd.MM.yyyy';
|
||||
|
||||
let value;
|
||||
if (inputFormat == 'iso')
|
||||
value = luxon.DateTime.fromISO(rowValue);
|
||||
else
|
||||
value = luxon.DateTime.fromFormat(rowValue, inputFormat);
|
||||
|
||||
let range = [];
|
||||
range = headerValue.split(' - ');
|
||||
if (range.length == 1) {
|
||||
range = headerValue.split('-');
|
||||
}
|
||||
if (range.length == 2) {
|
||||
range = range.map(r => {
|
||||
r = r.trim();
|
||||
if (r) {
|
||||
let d = luxon.DateTime.fromFormat(r, outputFormat);
|
||||
if (!d.isValid && inputFormat != 'iso' && inputFormat != outputFormat)
|
||||
d = luxon.DateTime.fromFormat(r, inputFormat);
|
||||
if (!d.isValid)
|
||||
d = luxon.DateTime.fromISO(r);
|
||||
if (d.isValid)
|
||||
return d;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (!range[0] && !range[1])
|
||||
return true;
|
||||
else if (!range[0])
|
||||
return this['<='](range[1], value, rowData, config);
|
||||
else if (!range[1])
|
||||
return this['>='](range[0], value, rowData, config);
|
||||
|
||||
let smaller = this['<='](range[1], value, rowData, config);
|
||||
let bigger = this['>='](range[0], value, rowData, config);
|
||||
return smaller && bigger;
|
||||
}
|
||||
|
||||
// fallback
|
||||
return this.like(headerValue, value.toFormat(outputFormat), rowData, config);
|
||||
}
|
||||
|
||||
export {
|
||||
dateinput
|
||||
};
|
||||
export default {
|
||||
dateinput
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user