Merge branch 'master' into feature-76781/Vertragsbestandteil_Kollektivvertrag

This commit is contained in:
Harald Bamberger
2026-07-15 07:30:00 +02:00
174 changed files with 13634 additions and 4592 deletions
+12 -1
View File
@@ -29,7 +29,7 @@ $config['ALLOWED_NOTEN_ABGABETOOL'] = [10, 14]; // tbl_note pk
// benotete projektarbeiten sperren weitere terminanlage & bearbeitung, diese noten sind ausnahmen dieser Regel // benotete projektarbeiten sperren weitere terminanlage & bearbeitung, diese noten sind ausnahmen dieser Regel
// wie zB "Nicht beurteilt" & "Noch nicht eingetragen" // wie zB "Nicht beurteilt" & "Noch nicht eingetragen"
$config['NONFINAL_NOTEN_ABGABETOOL'] = [9]; $config['NONFINAL_NOTEN_ABGABETOOL'] = [9];
$config['beurteilung_link_fallback'] = 'addons/fhtw/content/projektbeurteilung/projektbeurteilungDocumentExport.php?projektarbeit_id=?&betreuerart_kurzbz=?&person_id=?'; $config['beurteilung_link_fallback'] = 'cis/private/lehre/projektbeurteilungDocumentExport.php?projektarbeit_id=?&betreuerart_kurzbz=?&person_id=?';
$config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ERSTBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter'; $config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ERSTBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter';
$config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ZWEITBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter'; $config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ZWEITBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter';
@@ -38,8 +38,19 @@ $config['SIGNATUR_CHECK_PAABGABETYPEN'] = ['end'];
// to be used as "https://moodle.technikum-wien.at/course/view.php?idnumber=dl{$stg_kz}" for stg specific moodle routing // to be used as "https://moodle.technikum-wien.at/course/view.php?idnumber=dl{$stg_kz}" for stg specific moodle routing
$config['STG_MOODLE_LINK'] = 'https://moodle.technikum-wien.at/course/view.php?idnumber=dl'; $config['STG_MOODLE_LINK'] = 'https://moodle.technikum-wien.at/course/view.php?idnumber=dl';
// TODO: check if these links change if the file changes and how to better retrieve the link?
$config['SIGNATUR_INFO_LINK_GERMAN'] = 'https://cis.technikum-wien.at/cms/dms.php?id=214779';
$config['SIGNATUR_INFO_LINK_ENGLISH'] = 'https://cis.technikum-wien.at/cms/dms.php?id=264256';
$config['ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'] = true; $config['ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'] = true;
$config['ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'] = true; $config['ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'] = true;
$config['BETREUER_SAMMELMAIL_BUTTON_STUDENT'] = true; $config['BETREUER_SAMMELMAIL_BUTTON_STUDENT'] = true;
$config['MULTIEDIT_TABLE'] = true;
$config['STUDENT_EDIT_PROJEKTARBEIT_TITLE'] = true;
$config['CONFETTI_ON_ENDUPLOAD'] = true;
+3
View File
@@ -64,6 +64,9 @@ $route['api/v1/system/[S|s]prache/(:any)'] = 'api/v1/system/sprache2/$1';
$route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1'; $route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1';
$route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1'; $route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1';
$route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1'; $route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1';
$route['Cis/OtherLvPlan/.*'] = 'Cis/OtherLvPlan/index/$1';
//Route for LV Plan Stg/Semester/Verband/Gruppe
$route['Cis/StgOrgLvPlan/.*'] = 'Cis/StgOrgLvPlan/index/$1';
$route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz'; $route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz';
$route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1'; $route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1';
+2 -1
View File
@@ -22,8 +22,9 @@ unset($config['student']['searchfields']['email']);
unset($config['student']['searchfields']['tel']); unset($config['student']['searchfields']['tel']);
$config['student']['resultfields'] = [ $config['student']['resultfields'] = [
"s.student_uid AS uid", "s.student_uid AS uid",
"s.matrikelnr", "s.matrikelnr AS personenkennzeichen",
"p.person_id", "p.person_id",
"p.matr_nr AS matrikelnummer",
"(p.vorname || ' ' || p.nachname) AS name", "(p.vorname || ' ' || p.nachname) AS name",
"ARRAY[s.student_uid || '@' || '" . DOMAIN . "'] AS email", "ARRAY[s.student_uid || '@' || '" . DOMAIN . "'] AS email",
"CASE "CASE
+29
View File
@@ -168,3 +168,32 @@ $config['stgkz_blacklist_wiederholung'] = [];
* @var array An array of noten ids * @var array An array of noten ids
*/ */
$config['note_blacklist_wiederholung'] = []; $config['note_blacklist_wiederholung'] = [];
/**
* Enable/disable the Abmeldung StG process
*
* @var bool
*/
$config['abmeldung_stg_enabled'] = true;
/**
* Enable/disable the Abmeldung process
*
* @var bool
*/
$config['abmeldung_enabled'] = true;
/**
* Enable/disable the Unterbrecher process
*
* @var bool
*/
$config['unterbrechung_enabled'] = true;
/**
* Enable/disable the Wiederholer process
*
* @var bool
*/
$config['wiederholung_enabled'] = true;
+5 -26
View File
@@ -31,12 +31,8 @@ class Abgabetool extends Auth_Controller
{ {
// TODO: routing from index based on berechtigung? // TODO: routing from index based on berechtigung?
$viewData = array(
'uid'=>getAuthUID(),
);
if(defined('CIS4') && CIS4) { if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Abgabetool']); $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'Abgabetool']);
} else { } else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'Abgabetool']); $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'Abgabetool']);
} }
@@ -44,12 +40,8 @@ class Abgabetool extends Auth_Controller
public function Student($student_uid_prop = '') public function Student($student_uid_prop = '')
{ {
$viewData = array(
'uid'=>getAuthUID(),
);
if(defined('CIS4') && CIS4) { if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolStudent']); $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'AbgabetoolStudent']);
} else { } else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolStudent', 'student_uid_prop' => $student_uid_prop]); $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolStudent', 'student_uid_prop' => $student_uid_prop]);
} }
@@ -57,12 +49,8 @@ class Abgabetool extends Auth_Controller
public function Mitarbeiter() public function Mitarbeiter()
{ {
$viewData = array(
'uid'=>getAuthUID(),
);
if(defined('CIS4') && CIS4) { if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolMitarbeiter']); $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'AbgabetoolMitarbeiter']);
} else { } else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolMitarbeiter']); $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolMitarbeiter']);
} }
@@ -70,13 +58,8 @@ class Abgabetool extends Auth_Controller
public function Assistenz($stg_kz_prop = '') public function Assistenz($stg_kz_prop = '')
{ {
$viewData = array(
'uid'=>getAuthUID(),
);
if(defined('CIS4') && CIS4) { if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolAssistenz']); $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'AbgabetoolAssistenz']);
} else { } else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolAssistenz', 'stg_kz_prop' => $stg_kz_prop]); $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolAssistenz', 'stg_kz_prop' => $stg_kz_prop]);
} }
@@ -84,12 +67,8 @@ class Abgabetool extends Auth_Controller
public function Deadlines() public function Deadlines()
{ {
$viewData = array(
'uid'=>getAuthUID(),
);
if(defined('CIS4') && CIS4) { if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'DeadlinesOverview']); $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'DeadlinesOverview']);
} else { } else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'DeadlinesOverview']); $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'DeadlinesOverview']);
} }
+1 -1
View File
@@ -40,7 +40,7 @@ class Auth extends FHC_Controller
if ($this->form_validation->run()) if ($this->form_validation->run())
{ {
redirect($this->authlib->getLandingPage('/CisVue/Dashboard')); redirect($this->authlib->getLandingPage('/Cis4'));
} }
else else
{ {
+1 -7
View File
@@ -28,12 +28,6 @@ class LvPlan extends Auth_Controller
*/ */
public function index() public function index()
{ {
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'LvPlan']);
$viewData = array(
'uid'=>getAuthUID(),
'timezone' => $this->config->item('timezone')
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'LvPlan']);
} }
} }
+1 -6
View File
@@ -26,11 +26,6 @@ class MyLv extends Auth_Controller
*/ */
public function index() public function index()
{ {
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'MyLv']);
$viewData = array(
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'MyLv']);
} }
} }
+2 -8
View File
@@ -27,13 +27,7 @@ class MyLvPlan extends Auth_Controller
* @return void * @return void
*/ */
public function index() public function index()
{ {
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'MyLvPlan']);
$viewData = array(
'uid'=>getAuthUID(),
'timezone' => $this->config->item('timezone')
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'MyLvPlan']);
} }
} }
@@ -0,0 +1,34 @@
<?php
if (!defined('BASEPATH'))
exit('No direct script access allowed');
/**
*
*/
class OtherLvPlan extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct([
'index' => ['basis/other_lv_plan:r']
]);
// Load Config
$this->load->config('calendar');
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'OtherLvPlan']);
}
}
+19 -39
View File
@@ -55,15 +55,7 @@ class Profil extends Auth_Controller
*/ */
public function index() public function index()
{ {
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'profilIndex']);
$this->load->library('ProfilLib');
$profil_data = $this->profillib->getView(getAuthUID());
$profil_data = hasData($profil_data) ? getData($profil_data) : null;
$viewData = array(
'editable'=>true,
'profil_data' => $profil_data,
);
$this->load->view('CisRouterView/CisRouterView.php',['viewData' => $viewData, 'route' => 'profilIndex']);
} }
/** /**
@@ -73,23 +65,13 @@ class Profil extends Auth_Controller
*/ */
public function View($uid) public function View($uid)
{ {
$this->load->library('ProfilLib'); $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'profilViewUid']);
$profil_data = $this->profillib->getView($uid);
$profil_data = hasData($profil_data) ? getData($profil_data) : null;
$viewData = array (
'uid' => $uid,
'profil_data'=>$profil_data,
);
if($uid == getAuthUID()){
$viewData['editable'] = true;
}
$this->load->view('CisRouterView/CisRouterView.php',['viewData' => $viewData, 'route' => 'profilViewUid']);
} }
/** /**
* checks whether a specific userID is a mitarbeiter or not (foreword declaration of the function isMitarbeiter in Mitarbeiter_model.php) * checks whether a specific userID is a student or not (foreword declaration of the function isStudent in Student_model.php)
* @access public * @access public
* @param $uid the userID used to check if it is a mitarbeiter * @param $uid the userID used to check if it is a student
* @return boolean * @return boolean
*/ */
public function isStudent($uid) public function isStudent($uid)
@@ -119,7 +101,7 @@ class Profil extends Auth_Controller
} }
/** /**
* gets the adressen that are marked as zustell from the currenlty logged in user * gets the adressen that are marked as zustell from the currently logged in user
* @access public * @access public
* @return array a list of adresse_id's * @return array a list of adresse_id's
*/ */
@@ -262,23 +244,23 @@ class Profil extends Auth_Controller
$this->GemeindeModel->addDistinct(); $this->GemeindeModel->addDistinct();
$this->GemeindeModel->addSelect(["name"]); $this->GemeindeModel->addSelect(["name"]);
if ($nation == "A") { if ($nation == "A") {
if (isset($zip) && $zip > 999 && $zip < 32000) { if (isset($zip) && $zip > 999 && $zip < 32000) {
$gemeinde_res = $this->GemeindeModel->loadWhere(['plz' => $zip]); $gemeinde_res = $this->GemeindeModel->loadWhere(['plz' => $zip]);
if (isError($gemeinde_res)) { if (isError($gemeinde_res)) {
show_error("error while trying to query bis.tbl_gemeinde"); show_error("error while trying to query bis.tbl_gemeinde");
}
$gemeinde_res = hasData($gemeinde_res) ? getData($gemeinde_res) : null;
$gemeinde_res = array_map(function ($obj) {
return $obj->name;
}, $gemeinde_res);
echo json_encode($gemeinde_res);
} else {
echo json_encode(error("ortschaftskennziffer code was not valid"));
} }
$gemeinde_res = hasData($gemeinde_res) ? getData($gemeinde_res) : null;
$gemeinde_res = array_map(function ($obj) {
return $obj->name;
}, $gemeinde_res);
echo json_encode($gemeinde_res);
} else {
echo json_encode(error("ortschaftskennziffer code was not valid"));
}
} else { } else {
echo json_encode(error("Nation was not 'A' (Austria)")); echo json_encode(error("Nation was not 'A' (Austria)"));
} }
} }
@@ -750,6 +732,4 @@ class Profil extends Auth_Controller
$zutrittskarte_ausgegebenam = str_replace("-", ".", $zutrittskarte_ausgegebenam); $zutrittskarte_ausgegebenam = str_replace("-", ".", $zutrittskarte_ausgegebenam);
return $zutrittskarte_ausgegebenam; return $zutrittskarte_ausgegebenam;
} }
} }
+1 -6
View File
@@ -25,11 +25,6 @@ class Raumsuche extends Auth_Controller
*/ */
public function index() public function index()
{ {
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'Raumsuche']);
$viewData = array(
'uid'=>getAuthUID(),
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Raumsuche']);
} }
} }
@@ -0,0 +1,33 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class StgOrgLvPlan extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r']
]);
// Load Config
$this->load->config('calendar');
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
$this->load->view('CisRouterView/CisRouterView.php', ['route' => 'StgOrgLvPlan']);
}
}
+1 -4
View File
@@ -29,10 +29,7 @@ class Studium extends Auth_Controller
*/ */
public function index() public function index()
{ {
$viewData = array( $this->load->view('CisRouterView/CisRouterView.php',['route' => 'studium']);
);
$this->load->view('CisRouterView/CisRouterView.php',['viewData' => $viewData, 'route' => 'studium']);
} }
+6 -15
View File
@@ -1,6 +1,7 @@
<?php <?php
if (! defined('BASEPATH')) exit('No direct script access allowed'); if (!defined('BASEPATH'))
exit('No direct script access allowed');
/** /**
* *
@@ -13,9 +14,9 @@ class Cis4 extends Auth_Controller
public function __construct() public function __construct()
{ {
parent::__construct( parent::__construct(
array( array(
'index' => 'basis/cis:r' 'index' => 'basis/cis:r'
) )
); );
// Load Config // Load Config
@@ -30,16 +31,6 @@ class Cis4 extends Auth_Controller
*/ */
public function index() public function index()
{ {
$this->load->model('person/Person_model', 'PersonModel'); $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'FhcDashboard']);
$personData = getData($this->PersonModel->getByUid(getAuthUID()))[0];
$viewData = array(
'uid' => getAuthUID(),
'name' => $personData->vorname,
'person_id' => $personData->person_id,
'timezone' => $this->config->item('timezone')
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'FhcDashboard']);
} }
} }
@@ -1,43 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class Dashboard extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct(
array(
'index' => 'dashboard/benutzer:r'
)
);
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
$this->load->model('person/Person_model','PersonModel');
$personData = getData($this->PersonModel->getByUid(getAuthUID()))[0];
$viewData = array(
'uid' => getAuthUID(),
'name' => $personData->vorname,
'person_id' => $personData->person_id
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData]);
}
}
File diff suppressed because it is too large Load Diff
@@ -18,6 +18,8 @@
if (! defined('BASEPATH')) exit('No direct script access allowed'); if (! defined('BASEPATH')) exit('No direct script access allowed');
use \DateTime as DateTime;
class Bookmark extends FHCAPI_Controller class Bookmark extends FHCAPI_Controller
{ {
@@ -28,111 +30,162 @@ class Bookmark extends FHCAPI_Controller
{ {
parent::__construct([ parent::__construct([
'getBookmarks' => self::PERM_LOGGED, 'getBookmarks' => self::PERM_LOGGED,
'delete' => self::PERM_LOGGED, 'delete' => self::PERM_LOGGED,
'insert' => self::PERM_LOGGED, 'insert' => self::PERM_LOGGED,
'update' => self::PERM_LOGGED, 'update' => self::PERM_LOGGED,
]); 'changeOrder' => self::PERM_LOGGED
]);
$this->load->model('dashboard/Bookmark_model', 'BookmarkModel'); $this->load->model('dashboard/Bookmark_model', 'BookmarkModel');
$this->uid = getAuthUID(); $this->uid = getAuthUID();
$this->pid = getAuthPersonID(); $this->pid = getAuthPersonID();
} }
//------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------
// Public methods // Public methods
/** /**
* gets the bookmarks associated to a user * gets the bookmarks associated to a user
* @access public * @access public
* @return void * @return void
*/ */
public function getBookmarks() public function getBookmarks()
{ {
$this->BookmarkModel->addOrder("bookmark_id"); $this->BookmarkModel->addOrder("sort");
$bookmarks = $this->BookmarkModel->loadWhere(["uid"=>$this->uid]); $bookmarks = $this->BookmarkModel->loadWhere(["uid"=>$this->uid]);
$bookmarks = $this->getDataOrTerminateWithError($bookmarks); $bookmarks = $this->getDataOrTerminateWithError($bookmarks);
$this->terminateWithSuccess($bookmarks); $this->terminateWithSuccess($bookmarks);
} }
/**
* deletes bookmark from associated user
* @access public
* @return void
*/
public function delete($bookmark_id)
{
$bookmark = $this->BookmarkModel->load($bookmark_id);
$bookmark = current($this->getDataOrTerminateWithError($bookmark));
// only delete bookmark if the user is the owner of the bookmark
if($bookmark->uid == $this->uid || $this->permissionlib->isBerechtigt('admin')){
$delete_result = $this->BookmarkModel->delete($bookmark_id);
$delete_result = $this->getDataOrTerminateWithError($delete_result);
$this->terminateWithSuccess($delete_result);
}else{
$this->_outputAuthError(['delete' => ['admin:rw']]);
}
}
/**
* inserts new bookmark into the bookmark table
* @access public
* @return void
*/
public function insert()
{
// form validation
$this->load->library('form_validation');
$this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]');
$this->form_validation->set_rules('title', 'Title', 'required|max_length[255]');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
$url = $this->input->post('url',true);
$title = $this->input->post('title',true);
$tag = $this->input->post('tag', true);
$insert_into_result = $this->BookmarkModel->insert(['uid'=>$this->uid, 'url'=>$url, 'title'=>$title,'tag'=>$tag, 'insertvon'=>$this->uid, 'updateamum'=>NULL, 'updatevon'=>NULL]);
$insert_into_result = $this->getDataOrTerminateWithError($insert_into_result);
$this->terminateWithSuccess($insert_into_result);
}
/** /**
* updates bookmark in the bookmark table * deletes bookmark from associated user
* @access public * @access public
* @return void * @return void
*/ */
public function update($bookmark_id) public function delete($bookmark_id)
{ {
// form validation $bookmark = $this->BookmarkModel->load($bookmark_id);
$this->load->library('form_validation');
$this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]');
$this->form_validation->set_rules('title', 'Title', 'required|max_length[255]');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
$url = $this->input->post('url',true); $bookmark = current($this->getDataOrTerminateWithError($bookmark));
$title = $this->input->post('title',true);
// only delete bookmark if the user is the owner of the bookmark
if ($bookmark->uid == $this->uid || $this->permissionlib->isBerechtigt('admin')) {
$delete_result = $this->BookmarkModel->delete($bookmark_id);
$delete_result = $this->getDataOrTerminateWithError($delete_result);
$this->terminateWithSuccess($delete_result);
} else {
$this->_outputAuthError(['delete' => ['admin:rw']]);
}
}
/**
* inserts new bookmark into the bookmark table
* @access public
* @return void
*/
public function insert()
{
// form validation
$this->load->library('form_validation');
$this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]');
$this->form_validation->set_rules('title', 'Title', 'required|max_length[255]');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$url = $this->input->post('url', true);
$title = $this->input->post('title', true);
$tag = $this->input->post('tag', true);
if (is_array($tag)) {
$tag = json_encode($tag); // convert PHP array to JSON string
}
$sort = $this->input->post('sort', true);
$insert_into_result = $this->BookmarkModel->insert([
'uid' => $this->uid,
'url' => $url,
'title' => $title,
'tag' => $tag,
'insertvon' => $this->uid,
'updateamum' => null,
'updatevon' => null,
'sort' => $sort
]);
$insert_into_result = $this->getDataOrTerminateWithError($insert_into_result);
$this->terminateWithSuccess($insert_into_result);
}
/**
* updates bookmark in the bookmark table
* @access public
* @return void
*/
public function update($bookmark_id)
{
// form validation
$this->load->library('form_validation');
$this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]');
$this->form_validation->set_rules('title', 'Title', 'required|max_length[255]');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$url = $this->input->post('url', true);
$title = $this->input->post('title', true);
$tag = $this->input->post('tag', true);
if (is_array($tag)) {
$tag = json_encode($tag);
}
$now = new DateTime(); $now = new DateTime();
$now = $now->format('Y-m-d H:i:s'); $now = $now->format('Y-m-d H:i:s');
$update_result = $this->BookmarkModel->update($bookmark_id,['url'=>$url, 'title'=>$title,'updateamum'=>$now]); $update_result = $this->BookmarkModel->update($bookmark_id, [
'url' => $url,
'title' => $title,
'tag' => $tag,
'updateamum' => $now
]);
$update_result = $this->getDataOrTerminateWithError($update_result); $update_result = $this->getDataOrTerminateWithError($update_result);
$this->terminateWithSuccess($update_result); $this->terminateWithSuccess($update_result);
}
} /**
* changes sort of two bookmarks in the bookmark table
* @access public
* @return void
*/
public function changeOrder($bookmark_id1, $bookmark_id2)
{
$update_result = [];
$result1 = $this->BookmarkModel->load($bookmark_id1);
$data1 = $this->getDataOrTerminateWithError($result1);
$sort1 = current($data1)->sort;
$result2 = $this->BookmarkModel->load(["bookmark_id"=>$bookmark_id2]);
$data2 = $this->getDataOrTerminateWithError($result2);
$sort2 = current($data2)->sort;
$update_result1 = $this->BookmarkModel->update($bookmark_id1, [
'sort' => $sort2
]);
$update_result[] = $this->getDataOrTerminateWithError($update_result1);
$update_result2 = $this->BookmarkModel->update($bookmark_id2, [
'sort' => $sort1
]);
$update_result[] = $this->getDataOrTerminateWithError($update_result2);
$this->terminateWithSuccess($update_result);
}
} }
@@ -27,7 +27,7 @@ class Cis4FhcApi extends FHCAPI_Controller
public function __construct() public function __construct()
{ {
parent::__construct([ parent::__construct([
'getViewData' => self::PERM_LOGGED, 'dashboardViewData' => self::PERM_LOGGED,
]); ]);
} }
@@ -36,17 +36,22 @@ class Cis4FhcApi extends FHCAPI_Controller
// Public methods // Public methods
/** /**
* fetches ViewData * retrieves view data for dashboard view
*/ * @access public
public function getViewData() * @param $uid the userID for which profile is being viewed, null or missing value implies one's own profile
*/
public function dashboardViewData()
{ {
$this->load->model('person/Person_model','PersonModel'); $this->load->model('person/Person_model','PersonModel');
$personData = getData($this->PersonModel->getByUid(getAuthUID()))[0]; $personData = getData($this->PersonModel->getByUid(getAuthUID()))[0];
$this->load->config('calendar');
$viewData = array( $viewData = array(
'uid' => getAuthUID(), 'uid' => getAuthUID(),
'name' => $personData->vorname, 'name' => $personData->vorname,
'person_id' => $personData->person_id 'person_id' => $personData->person_id,
'timezone' => $this->config->item('timezone'),
); );
$this->terminateWithSuccess($viewData); $this->terminateWithSuccess($viewData);
@@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
if (! defined('BASEPATH')) exit('No direct script access allowed'); if (!defined('BASEPATH'))
exit('No direct script access allowed');
class CisMenu extends FHCAPI_Controller class CisMenu extends FHCAPI_Controller
{ {
/** /**
* Object initialization * Object initialization
*/ */
@@ -31,28 +32,95 @@ class CisMenu extends FHCAPI_Controller
'getMenu' => self::PERM_LOGGED, 'getMenu' => self::PERM_LOGGED,
]); ]);
} }
//------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------
// Public methods // Public methods
/** /**
* fetches the menu for CIS from the database based on the userLanguage * fetches the menu for CIS from the database based on the userLanguage
*/ */
public function getMenu() public function getMenu()
{ {
$this->load->model('content/Content_model', 'ContentModel'); $this->load->model('content/Content_model', 'ContentModel');
$this->load->config('cis'); $this->load->config('cis');
$cis4_content_id =$this->config->item('cis_menu_root_content_id'); $cis4_content_id = $this->config->item('cis_menu_root_content_id');
$result = $this->ContentModel->getMenu($cis4_content_id, getAuthUID(),getUserLanguage()); $result = $this->ContentModel->getMenu($cis4_content_id, getAuthUID(), getUserLanguage());
$result = $this->getDataOrTerminateWithError($result); $result = $this->getDataOrTerminateWithError($result);
$menu = $result->childs ?? []; $menu = $result->childs ?? [];
$this->terminateWithSuccess($menu);
}
$menu = $this->generateUrlsForMenuItems($menu);
$this->terminateWithSuccess($menu);
}
private function generateUrlsForMenuItems($menuItems)
{
return array_map(
function ($menuItem) {
return $this->generateUrlForMenuItem($menuItem);
},
$menuItems
);
}
private function generateUrlForMenuItem($menuItem)
{
$menuItem->url = $this->menuItemUrlHelper($menuItem);
unset($menuItem->content);
if ($menuItem->childs && count($menuItem->childs)) {
$menuItem->childs = $this->generateUrlsForMenuItems($menuItem->childs);
}
return $menuItem;
}
private function menuItemUrlHelper($menuItem)
{
if ($menuItem->template_kurzbz !== 'redirect') {
return site_url("/CisVue/Cms/content/" . $menuItem->content_id);
}
if (!$menuItem->content || !mb_strlen($menuItem->content)) {
return '';
}
$doc = new DOMDocument();
$doc->loadXML($menuItem->content);
$urlElem = $doc->getElementsByTagName('url')->item(0);
if (!$urlElem) {
return '';
}
$url = $urlElem->textContent;
if (strpos($url, '../cms/news.php') !== false) {
$newsRegex = '/^\.\.\/cms\/news\.php/';
$url = preg_replace($newsRegex, site_url("/CisVue/Cms/news"), $url);
}
if (strpos($url, '../cms/content.php?') !== false) {
$contentRegex = '/^\.\.\/cms\/content\.php\?content_id=([0-9]+)/';
$matches = [];
preg_match($contentRegex, $url, $matches);
$url = site_url('/CisVue/Cms/content/' . $matches[1]);
}
if (strpos($url, '../index.ci.php') !== false) {
$indexRegex = '/^\.\.\/index\.ci\.php/';
$url = preg_replace($indexRegex, site_url(), $url);
}
if (strpos($url, '../') !== false) {
$relativeRegex = '/^\.\.\//';
$url = preg_replace($relativeRegex, base_url(), $url);
}
return $url;
}
} }
@@ -38,7 +38,8 @@ class Lehre extends FHCAPI_Controller
parent::__construct([ parent::__construct([
'lvStudentenMail' => self::PERM_LOGGED, 'lvStudentenMail' => self::PERM_LOGGED,
'LV' => self::PERM_LOGGED, 'LV' => self::PERM_LOGGED,
'Pruefungen' => self::PERM_LOGGED 'Pruefungen' => self::PERM_LOGGED,
'semesterAverageGrade' => self::PERM_LOGGED,
]); ]);
} }
@@ -100,5 +101,49 @@ class Lehre extends FHCAPI_Controller
$this->terminateWithSuccess($result); $this->terminateWithSuccess($result);
} }
/**
* calculates and returns the grade average and weighted average for a specific semester
* @param string $studiensemester_kurzbz
* @return void
*/
public function semesterAverageGrade($studiensemester_kurzbz)
{
$this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
$semesterLvs = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage());
if (isError($semesterLvs))
return $this->outputJsonError(getError($semesterLvs));
$semesterLvsData = getData($semesterLvs);
$doGradesExist = false;
$sum = 0;
$count = 0;
$sumWeighted = 0;
$sumEcts = 0;
foreach ($semesterLvsData as $lv) {
if (!$lv->znote || $lv->znote < 1 || $lv->znote > 5)
continue;
$doGradesExist = true;
$sum += $lv->znote;
$count++;
$sumWeighted += $lv->znote * floatval($lv->ects);
$sumEcts += floatval($lv->ects);
}
$averageGrade = null;
$weightedAverageGrade = null;
if ($doGradesExist) {
$averageGrade = $sum/$count;
$weightedAverageGrade = $sumWeighted/$sumEcts;
}
$this->terminateWithSuccess(['average_grade' => $averageGrade, 'weighted_average_grade' => $weightedAverageGrade]);
}
} }
@@ -16,7 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
if (! defined('BASEPATH')) exit('No direct script access allowed'); if (!defined('BASEPATH'))
exit('No direct script access allowed');
use CI3_Events as Events; use CI3_Events as Events;
use \DateTime as DateTime; use \DateTime as DateTime;
@@ -33,19 +34,25 @@ class LvPlan extends FHCAPI_Controller
parent::__construct([ parent::__construct([
'getRoomplan' => self::PERM_LOGGED, 'getRoomplan' => self::PERM_LOGGED,
'Stunden' => self::PERM_LOGGED, 'Stunden' => self::PERM_LOGGED,
'getReservierungen' => self::PERM_LOGGED, 'getReservierungen' => self::PERM_LOGGED,
'LvPlanEvents' => self::PERM_LOGGED, 'LvPlanEvents' => self::PERM_LOGGED,
'eventsPersonal' => self::PERM_LOGGED, 'eventsPersonal' => self::PERM_LOGGED,
'eventsLv' => self::PERM_LOGGED, 'eventsLv' => self::PERM_LOGGED,
'getLehreinheitStudiensemester' => self::PERM_LOGGED, 'getLehreinheitStudiensemester' => self::PERM_LOGGED,
'studiensemesterDateInterval' => self::PERM_LOGGED, 'studiensemesterDateInterval' => self::PERM_LOGGED,
'getLvPlanForStudiensemester' => self::PERM_LOGGED, 'getLvPlanForStudiensemester' => self::PERM_LOGGED,
'getLv' => self::PERM_LOGGED 'getLv' => self::PERM_LOGGED,
'eventsStgOrg' => self::PERM_LOGGED,
'fetchFerienEvents' => self::PERM_LOGGED,
'getStudiengaenge' => self::PERM_LOGGED,
'getLehrverband' => self::PERM_LOGGED,
'permissionOtherLvPlan' => self::PERM_LOGGED,
'compactibleEventTypes' => self::PERM_LOGGED,
]); ]);
$this->load->library('LogLib'); $this->load->library('LogLib');
$this->loglib->setConfigs(array( $this->loglib->setConfigs(array(
'classIndex' => 5, 'classIndex' => 5,
'functionIndex' => 5, 'functionIndex' => 5,
'lineIndex' => 4, 'lineIndex' => 4,
@@ -53,17 +60,17 @@ class LvPlan extends FHCAPI_Controller
'dbExecuteUser' => 'RESTful API' 'dbExecuteUser' => 'RESTful API'
)); ));
$this->load->library('form_validation'); $this->load->library('form_validation');
} }
//------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------
// Public methods // Public methods
/** /**
* fetches LvPlan and Moodle events together * fetches LvPlan and Moodle events together
* @access public * @access public
* *
*/ */
public function LvPlanEvents() public function LvPlanEvents()
{ {
$hasLv = $this->input->post('lv_id'); $hasLv = $this->input->post('lv_id');
@@ -83,24 +90,30 @@ class LvPlan extends FHCAPI_Controller
// form validation // form validation
$this->form_validation->set_rules('start_date', "start_date", "required"); $this->form_validation->set_rules('start_date', "start_date", "required");
$this->form_validation->set_rules('end_date', "end_date", "required"); $this->form_validation->set_rules('end_date', "end_date", "required");
if (!$this->form_validation->run()) if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array()); $this->terminateWithValidationErrors($this->form_validation->error_array());
// storing the post parameter in local variables // storing the post parameter in local variables
$start_date = $this->input->post('start_date', true); $start_date = $this->input->post('start_date', true);
$end_date = $this->input->post('end_date', true); $end_date = $this->input->post('end_date', true);
$uid = $this->input->post('uid', true);
// disallow accessing other user's events if missing permission
if ($uid && $uid !== getAuthUID() && !$this->permissionlib->isBerechtigt('basis/other_lv_plan')) {
$this->terminateWithError("Missing permission to view other users' timetables!");
}
// fetching lvplan events // fetching lvplan events
$result = $this->stundenplanlib->getEventsUser($start_date, $end_date); $result = $this->stundenplanlib->getEventsUser($start_date, $end_date, $uid);
$lvplanEvents = $this->getDataOrTerminateWithError($result); $lvplanEvents = $this->getDataOrTerminateWithError($result);
// fetching moodle events // fetching moodle events
$moodleEvents = $this->fetchMoodleEvents($start_date, $end_date); $moodleEvents = $this->fetchMoodleEvents($start_date, $end_date, $uid);
// fetching ferien events // fetching ferien events
$ferienEvents = $this->fetchFerienEvents($start_date, $end_date); $ferienEvents = $this->fetchFerienEvents($start_date, $end_date, $uid);
$this->terminateWithSuccess(array_merge( $this->terminateWithSuccess(array_merge(
$lvplanEvents, $lvplanEvents,
@@ -109,6 +122,45 @@ class LvPlan extends FHCAPI_Controller
)); ));
} }
/**
* fetches LvPlan for studiengang / semester / verband / gruppe
*
* @access public
*/
public function eventsStgOrg()
{
$this->load->library('StundenplanLib');
// form validation
$this->form_validation->set_rules('start_date', "start_date", "required");
$this->form_validation->set_rules('end_date', "end_date", "required");
//$this->form_validation->set_rules('stg_kz', "stg_kz", "required"); //no validation show empty calendar
if (!$this->form_validation->run()) {
$this->terminateWithValidationErrors($this->form_validation->error_array());
$stgOrgEvents = [];
$ferienEvents = [];
} else {
$start_date = $this->input->post('start_date', true);
$end_date = $this->input->post('end_date', true);
$stg_kz = $this->input->post('stg_kz', true);
$sem = $this->input->post('sem', true);
$verband = $this->input->post('verband', true);
$gruppe = $this->input->post('gruppe', true);
$result = $this->stundenplanlib->getEventsStgOrg($start_date, $end_date, $stg_kz, $sem, $verband, $gruppe);
$stgOrgEvents = $this->getDataOrTerminateWithError($result);
$result = $this->stundenplanlib->fetchFerienTageEvents($start_date, $end_date, $stg_kz);
$ferienEvents = $this->getDataOrTerminateWithError($result);
}
$this->terminateWithSuccess(array_merge(
$stgOrgEvents,
$ferienEvents
));
}
/** /**
* fetches LvPlan and Ferien events together for the lv * fetches LvPlan and Ferien events together for the lv
* *
@@ -122,7 +174,7 @@ class LvPlan extends FHCAPI_Controller
$this->form_validation->set_rules('start_date', "start_date", "required"); $this->form_validation->set_rules('start_date', "start_date", "required");
$this->form_validation->set_rules('end_date', "end_date", "required"); $this->form_validation->set_rules('end_date', "end_date", "required");
$this->form_validation->set_rules('lv_id', "lv_id", "required|integer"); $this->form_validation->set_rules('lv_id', "lv_id", "required|integer");
if (!$this->form_validation->run()) if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array()); $this->terminateWithValidationErrors($this->form_validation->error_array());
@@ -137,7 +189,6 @@ class LvPlan extends FHCAPI_Controller
// fetching ferien events // fetching ferien events
$ferienEvents = $this->fetchFerienEvents($start_date, $end_date); $ferienEvents = $this->fetchFerienEvents($start_date, $end_date);
$this->terminateWithSuccess(array_merge( $this->terminateWithSuccess(array_merge(
$lvplanEvents, $lvplanEvents,
@@ -146,40 +197,42 @@ class LvPlan extends FHCAPI_Controller
} }
//TODO: delete this function if we don't use the old calendar export endpoints anymore //TODO: delete this function if we don't use the old calendar export endpoints anymore
public function studiensemesterDateInterval($date){ public function studiensemesterDateInterval($date)
$this->load->model('organisation/Studiensemester_model','StudiensemesterModel'); {
$studiensemester =$this->StudiensemesterModel->getByDate(date_format(date_create($date),'Y-m-d')); $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
$studiensemester =current($this->getDataOrTerminateWithError($studiensemester)); $studiensemester = $this->StudiensemesterModel->getByDate(date_format(date_create($date), 'Y-m-d'));
$studiensemester = current($this->getDataOrTerminateWithError($studiensemester));
$this->terminateWithSuccess($studiensemester); $this->terminateWithSuccess($studiensemester);
} }
public function getLvPlanForStudiensemester($studiensemester,$lvid){ public function getLvPlanForStudiensemester($studiensemester, $lvid)
{
$this->load->library('StundenplanLib'); $this->load->library('StundenplanLib');
$this->load->model('organisation/Studiensemester_model','StudiensemesterModel'); $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
$studiensemester_result = $this->StudiensemesterModel->loadWhere(["studiensemester_kurzbz"=>$studiensemester]); $studiensemester_result = $this->StudiensemesterModel->loadWhere(["studiensemester_kurzbz" => $studiensemester]);
$studiensemester_result = current($this->getDataOrTerminateWithError($studiensemester_result)); $studiensemester_result = current($this->getDataOrTerminateWithError($studiensemester_result));
$timespan_start = new DateTime($studiensemester_result->start); $timespan_start = new DateTime($studiensemester_result->start);
$timespan_ende = new DateTime($studiensemester_result->ende); $timespan_ende = new DateTime($studiensemester_result->ende);
$lvplan = $this->stundenplanlib->getStundenplan(date_format($timespan_start, 'Y-m-d'),date_format($timespan_ende, 'Y-m-d'), $lvid); $lvplan = $this->stundenplanlib->getStundenplan(date_format($timespan_start, 'Y-m-d'), date_format($timespan_ende, 'Y-m-d'), $lvid);
$this->terminateWithSuccess($lvplan); $this->terminateWithSuccess($lvplan);
}
/** }
* fetches Stunden layout from database
* @access public
* /**
*/ * fetches Stunden layout from database
public function Stunden() * @access public
*
*/
public function Stunden()
{ {
$this->load->model('ressource/Stunde_model', 'StundeModel'); $this->load->model('ressource/Stunde_model', 'StundeModel');
$this->StundeModel->addOrder('stunde', 'ASC'); $this->StundeModel->addOrder('stunde', 'ASC');
$stunden = $this->StundeModel->load(); $stunden = $this->StundeModel->load();
$stunden = $this->getDataOrTerminateWithError($stunden); $stunden = $this->getDataOrTerminateWithError($stunden);
$this->terminateWithSuccess($stunden); $this->terminateWithSuccess($stunden);
} }
@@ -210,10 +263,10 @@ class LvPlan extends FHCAPI_Controller
$roomplan_data = $this->stundenplanlib->getRoomplan($ort_kurzbz, $start_date, $end_date); $roomplan_data = $this->stundenplanlib->getRoomplan($ort_kurzbz, $start_date, $end_date);
$roomplan_data = $this->getDataOrTerminateWithError($roomplan_data); $roomplan_data = $this->getDataOrTerminateWithError($roomplan_data);
$this->terminateWithSuccess($roomplan_data); $this->terminateWithSuccess($roomplan_data);
} }
/** /**
* gets the reservierungen of a room if the ort_kurzbz parameter is * gets the reservierungen of a room if the ort_kurzbz parameter is
* supplied otherwise gets the reservierungen of the lvplan of a student * supplied otherwise gets the reservierungen of the lvplan of a student
@@ -226,25 +279,32 @@ class LvPlan extends FHCAPI_Controller
{ {
$this->form_validation->set_rules('start_date', "StartDate", "required"); $this->form_validation->set_rules('start_date', "StartDate", "required");
$this->form_validation->set_rules('end_date', "EndDate", "required"); $this->form_validation->set_rules('end_date', "EndDate", "required");
if (!$this->form_validation->run()) if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array()); $this->terminateWithValidationErrors($this->form_validation->error_array());
// storing the post parameter in local variables // storing the post parameter in local variables
$start_date = $this->input->post('start_date', true); $start_date = $this->input->post('start_date', true);
$end_date = $this->input->post('end_date', true); $end_date = $this->input->post('end_date', true);
$uid = $this->input->post('uid', true);
// disallow accessing other user's reservierungen if missing permission
if ($uid && $uid !== getAuthUID() && !$this->permissionlib->isBerechtigt('basis/other_lv_plan')) {
$this->terminateWithError("Missing permission to view other users' timetables!");
}
// get data // get data
$this->load->library('StundenplanLib'); $this->load->library('StundenplanLib');
$result = $this->stundenplanlib->getReservierungen($start_date, $end_date, $ort_kurzbz); $result = $this->stundenplanlib->getReservierungen($start_date, $end_date, $ort_kurzbz, $uid);
$result = $this->getDataOrTerminateWithError($result); $result = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result); $this->terminateWithSuccess($result);
} }
public function getLehreinheitStudiensemester($lehreinheit_id){ public function getLehreinheitStudiensemester($lehreinheit_id)
{
$this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); $this->load->model('education/Lehreinheit_model', 'LehreinheitModel');
$this->LehreinheitModel->addSelect(["studiensemester_kurzbz"]); $this->LehreinheitModel->addSelect(["studiensemester_kurzbz"]);
$result = $this->LehreinheitModel->load($lehreinheit_id); $result = $this->LehreinheitModel->load($lehreinheit_id);
@@ -287,6 +347,68 @@ class LvPlan extends FHCAPI_Controller
return $this->terminateWithSuccess(current($result)); return $this->terminateWithSuccess(current($result));
} }
public function getStudiengaenge()
{
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->StudiengangModel->addOrder('typ');
$this->StudiengangModel->addOrder('kurzbz');
$result = $this->StudiengangModel->loadWhere([
'aktiv' => true
]);
$data = $this->getDataOrTerminateWithError($result);
return $this->terminateWithSuccess($data);
}
public function getLehrverband($studiengang_kz, $semester = null, $verband = null)
{
$this->load->model('organisation/Lehrverband_model', 'LehrverbandModel');
$where = [
'aktiv' => true,
'studiengang_kz' => $studiengang_kz,
];
if ($semester !== null && $semester !== 'null' && $semester !== 'undefined') {
$where['semester'] = $semester;
}
if ($verband !== null && $verband !== 'null' && $verband !== 'undefined') {
$where['verband'] = $verband;
}
$this->LehrverbandModel->addOrder('studiengang_kz');
$this->LehrverbandModel->addOrder('semester');
$this->LehrverbandModel->addOrder('verband');
$this->LehrverbandModel->addOrder('gruppe');
$result = $this->LehrverbandModel->loadWhere($where);
$data = $this->getDataOrTerminateWithError($result);
return $this->terminateWithSuccess($data);
}
/**
* Checks if the current user has permission to view other users' timetables
*
* @return void
*/
public function permissionOtherLvPlan()
{
$this->terminateWithSuccess($this->permissionlib->isBerechtigt('basis/other_lv_plan'));
}
/**
* get event types which can be compacted in lv plan display
*
* @return void
*/
public function compactibleEventTypes()
{
$this->terminateWithSuccess(["lehreinheit", "reservierung", "ferien", "moodle"]);
}
/** /**
* fetch moodle events * fetch moodle events
* *
@@ -294,30 +416,34 @@ class LvPlan extends FHCAPI_Controller
* @param string $end_date * @param string $end_date
* @return array * @return array
*/ */
private function fetchMoodleEvents($start_date, $end_date) private function fetchMoodleEvents($start_date, $end_date, $uid = null)
{ {
if ($uid && $uid !== getAuthUID()) {
return [];
}
$this->load->config('calendar'); $this->load->config('calendar');
$tz = new DateTimeZone($this->config->item('timezone')); $tz = new DateTimeZone($this->config->item('timezone'));
$start = new DateTime($start_date); $start = new DateTime($start_date);
$start->setTimezone($tz); $start->setTimezone($tz);
$end = new DateTime($end_date); $end = new DateTime($end_date);
$end->setTimezone($tz); $end->setTimezone($tz);
$end->modify('+1 day -1 second'); $end->modify('+1 day -1 second');
$moodle_events = []; $moodle_events = [];
Events::trigger( Events::trigger(
'moodleCalendarEvents', 'moodleCalendarEvents',
function & () use (&$moodle_events) { function &() use (&$moodle_events) {
return $moodle_events; return $moodle_events;
}, },
[ [
'start_date' => $start->format('c'), 'start_date' => $start->format('c'),
'end_date' => $end->format('c'), 'end_date' => $end->format('c'),
'username' => getAuthUID() 'username' => $uid ?? getAuthUID()
] ]
); );
@@ -331,23 +457,23 @@ class LvPlan extends FHCAPI_Controller
* @param string $end_date * @param string $end_date
* @return array * @return array
*/ */
private function fetchFerienEvents($start_date, $end_date) private function fetchFerienEvents($start_date, $end_date, $uid = null)
{ {
$this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
$this->load->model('education/Studentlehrverband_model', 'StudentLehrverbandModel'); $this->load->model('education/Studentlehrverband_model', 'StudentLehrverbandModel');
$currentStudiensemester = $this->StudiensemesterModel->getByDate($start_date); $currentStudiensemester = $this->StudiensemesterModel->getByDate($start_date);
$currentStudiensemester = $this->getDataOrTerminateWithError($currentStudiensemester); $currentStudiensemester = $this->getDataOrTerminateWithError($currentStudiensemester);
if ($currentStudiensemester) { if ($currentStudiensemester) {
$studentsemester_kurzbz = current($currentStudiensemester)->studiensemester_kurzbz; $studentsemester_kurzbz = current($currentStudiensemester)->studiensemester_kurzbz;
$studiengang = $this->StudentLehrverbandModel->loadWhere([ $studiengang = $this->StudentLehrverbandModel->loadWhere([
"student_uid" => getAuthUID(), "student_uid" => $uid ?? getAuthUID(),
"studiensemester_kurzbz" => $studentsemester_kurzbz "studiensemester_kurzbz" => $studentsemester_kurzbz
]); ]);
$studiengang = $this->getDataOrTerminateWithError($studiengang); $studiengang = $this->getDataOrTerminateWithError($studiengang);
if ($studiengang) if ($studiengang)
$studiengang_kz = current($studiengang)->studiengang_kz; $studiengang_kz = current($studiengang)->studiengang_kz;
else else
@@ -357,7 +483,7 @@ class LvPlan extends FHCAPI_Controller
} }
$ferienEvents = $this->stundenplanlib->fetchFerienTageEvents($start_date, $end_date, $studiengang_kz); $ferienEvents = $this->stundenplanlib->fetchFerienTageEvents($start_date, $end_date, $studiengang_kz);
return $this->getDataOrTerminateWithError($ferienEvents); return $this->getDataOrTerminateWithError($ferienEvents);
} }
} }
@@ -0,0 +1,76 @@
<?php
/**
* Copyright (C) 2024 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/>.
*/
if (!defined('BASEPATH'))
exit('No direct script access allowed');
class OtherLvPlan extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'otherLvPlanViewData' => ['basis/other_lv_plan:r'],
]);
$this->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel');
$this->load->model('person/Benutzer_model', 'BenutzerModel');
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* retrieves viewData for other lv plan view
* @access public
* @param $uid the userID for which the other lv plan is being viewed
*/
public function otherLvPlanViewData($uid)
{
$isMitarbeiterResult = $this->MitarbeiterModel->isMitarbeiter($uid);
$isMitarbeiter = getData($isMitarbeiterResult);
$isStudent = !$isMitarbeiter;
$this->BenutzerModel->addSelect(["foto", "vorname", "nachname"]);
$this->BenutzerModel->addJoin("tbl_person", "person_id");
$personResult = $this->BenutzerModel->load([$uid]);
$person = hasData($personResult) ? getData($personResult) : null;
$viewData = [
"user_data" => [
"username" => $uid,
"is_student" => $isStudent,
"is_mitarbeiter" => $isMitarbeiter,
"foto" => $person[0]->foto,
"vorname" => $person[0]->vorname,
"nachname" => $person[0]->nachname,
],
];
$this->terminateWithSuccess($viewData);
}
// -----------------------------------------------------------------------------------------------------------------
// Private methods
}
@@ -32,6 +32,8 @@ class Phrasen extends FHCAPI_Controller
'setLanguage' => self::PERM_ANONYMOUS, 'setLanguage' => self::PERM_ANONYMOUS,
'getLanguage' => self::PERM_ANONYMOUS, 'getLanguage' => self::PERM_ANONYMOUS,
'getAllLanguages' => self::PERM_ANONYMOUS, 'getAllLanguages' => self::PERM_ANONYMOUS,
'getPhrases' => self::PERM_ANONYMOUS,
'getTabulatorPhrases' => self::PERM_ANONYMOUS,
]); ]);
$this->load->helper('hlp_language'); $this->load->helper('hlp_language');
@@ -92,4 +94,59 @@ class Phrasen extends FHCAPI_Controller
$this->terminateWithSuccess($langs); $this->terminateWithSuccess($langs);
} }
public function getPhrases()
{
$postParams = $this->getPostJSON();
$languages = $postParams->languages;
if (!$languages || !count($languages)) {
$this->load->model('system/Sprache_model', 'sprachenModel');
$activeLanguages = $this->sprachenModel->loadWhere(array('content' => true));
$activeLanguagesData = $this->getDataOrTerminateWithError($activeLanguages);
$languages = array_map(
function ($languageData) {
return $languageData->sprache;
},
$activeLanguagesData
);
}
$this->load->model('system/Phrase_model', 'phraseModel');
$phrasesGroupedByCategory = $postParams->phrasesGroupedByCategory;
$result = [];
foreach ($languages as $language) {
$phrases = $this->phraseModel->getPhrasesByCategoryAndPhrasesAndLanguage($phrasesGroupedByCategory, $language);
$result[$language] = $this->getDataOrTerminateWithError($phrases);
}
$this->terminateWithSuccess($result);
}
public function getTabulatorPhrases()
{
$languages = json_decode($this->input->get('languages'));
if (!$languages || !count($languages)) {
$this->load->model('system/Sprache_model', 'sprachenModel');
$activeLanguages = $this->sprachenModel->loadWhere(array('content' => true));
$activeLanguagesData = $this->getDataOrTerminateWithError($activeLanguages);
$languages = array_map(
function ($languageData) {
return $languageData->sprache;
},
$activeLanguagesData
);
}
$this->load->model('system/Phrase_model', 'phraseModel');
$result = [];
foreach ($languages as $language) {
$tabulatorPhrases = $this->phraseModel->getPhrasesByCategoryAndLanguage(['tabulator'], $language);
$result[$language] = $this->getDataOrTerminateWithError($tabulatorPhrases);
}
header('Pragma: private');
header('Cache-Control: private, max-age=' . (60 * 60 * 24 * 30));
$this->terminateWithSuccess($result);
}
} }
@@ -16,7 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
if (! defined('BASEPATH')) exit('No direct script access allowed'); if (!defined('BASEPATH'))
exit('No direct script access allowed');
class Profil extends FHCAPI_Controller class Profil extends FHCAPI_Controller
{ {
@@ -27,13 +28,13 @@ class Profil extends FHCAPI_Controller
public function __construct() public function __construct()
{ {
parent::__construct([ parent::__construct([
'fotoSperre' => self::PERM_LOGGED, 'fotoSperre' => self::PERM_LOGGED,
'getGemeinden' => self::PERM_LOGGED, 'getGemeinden' => self::PERM_LOGGED,
'getAllNationen' => self::PERM_LOGGED, 'getAllNationen' => self::PERM_LOGGED,
'isMitarbeiter' => self::PERM_LOGGED, 'isMitarbeiter' => self::PERM_LOGGED,
'profilViewData' => self::PERM_LOGGED, 'profilViewData' => self::PERM_LOGGED,
]); ]);
$this->load->library('PermissionLib'); $this->load->library('PermissionLib');
$this->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel'); $this->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel');
@@ -48,28 +49,37 @@ class Profil extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------
// Public methods // Public methods
public function profilViewData($uid=null){
/**
* retrieves view data for profile view
* @access public
* @param $uid the userID for which profile is being viewed, null or missing value implies one's own profile
*/
public function profilViewData($uid = null)
{
$authUid = getAuthUID();
$isProfilOfAuthUser = !$uid || $uid === $authUid;
$this->load->library('ProfilLib'); $this->load->library('ProfilLib');
$editable = false; $profileData = $this->profillib->getView($uid ?? $authUid);
if(isset($uid) && $uid != null){ $profileData = hasData($profileData) ? getData($profileData) : null;
$profil_data = $this->profillib->getView($uid);
if($uid == getAuthUID()){ $viewData = [
$editable = true; 'editable' => $isProfilOfAuthUser,
} 'profil_data' => $profileData,
}else{ 'permissions' => [
$editable = true; 'basis/other_lv_plan' => $this->permissionlib->isBerechtigt(('basis/other_lv_plan'))
$profil_data = $this->profillib->getView(getAuthUID()); ]
];
if ($isProfilOfAuthUser) {
$viewData['calendar_sync_urls'] = $this->getCalendarSyncUrlData();
} }
$profil_data = hasData($profil_data) ? getData($profil_data) : null;
$viewData = array(
'editable'=>$editable,
'profil_data' => $profil_data,
);
$this->terminateWithSuccess($viewData); $this->terminateWithSuccess($viewData);
} }
/** /**
* update column foto_sperre in public.tbl_person * update column foto_sperre in public.tbl_person
* @access public * @access public
* @param boolean $value new value for the column * @param boolean $value new value for the column
@@ -77,9 +87,9 @@ class Profil extends FHCAPI_Controller
*/ */
public function fotoSperre($value) public function fotoSperre($value)
{ {
if(!isset($value)){ if (!isset($value)) {
$this->terminateWithError("Missing parameter", self::ERROR_TYPE_GENERAL); $this->terminateWithError("Missing parameter", self::ERROR_TYPE_GENERAL);
} }
$res = $this->PersonModel->update($this->pid, ["foto_sperre" => $value]); $res = $this->PersonModel->update($this->pid, ["foto_sperre" => $value]);
if (isError($res)) { if (isError($res)) {
@@ -87,10 +97,10 @@ class Profil extends FHCAPI_Controller
} }
$this->PersonModel->addSelect("foto_sperre"); $this->PersonModel->addSelect("foto_sperre");
$res = $this->PersonModel->load($this->pid); $res = $this->PersonModel->load($this->pid);
$res = $this->getDataOrTerminateWithError($res); $res = $this->getDataOrTerminateWithError($res);
$this->terminateWithSuccess(current($res)); $this->terminateWithSuccess(current($res));
} }
/** /**
@@ -109,7 +119,7 @@ class Profil extends FHCAPI_Controller
if (isError($nation_res)) { if (isError($nation_res)) {
$this->terminateWithError("error while trying to query table codex.tbl_nation", self::ERROR_TYPE_GENERAL); $this->terminateWithError("error while trying to query table codex.tbl_nation", self::ERROR_TYPE_GENERAL);
} }
$nation_res = $this->getDataOrTerminateWithError($nation_res); $nation_res = $this->getDataOrTerminateWithError($nation_res);
$this->terminateWithSuccess($nation_res); $this->terminateWithSuccess($nation_res);
@@ -117,30 +127,30 @@ class Profil extends FHCAPI_Controller
public function getGemeinden($nation, $zip) public function getGemeinden($nation, $zip)
{ {
if(!isset($nation) || !isset($zip)){ if (!isset($nation) || !isset($zip)) {
echo json_encode(error("Missing parameters")); echo json_encode(error("Missing parameters"));
return; return;
} }
$this->load->model('codex/Gemeinde_model', "GemeindeModel"); $this->load->model('codex/Gemeinde_model', "GemeindeModel");
$gemeinde_res = $this->GemeindeModel->getGemeindeByPlz($zip); $gemeinde_res = $this->GemeindeModel->getGemeindeByPlz($zip);
if (isError($gemeinde_res)) { if (isError($gemeinde_res)) {
$this->terminateWithError(getError($gemeinde_res),self::ERROR_TYPE_GENERAL); $this->terminateWithError(getError($gemeinde_res), self::ERROR_TYPE_GENERAL);
} }
$gemeinde_res = $this->getDataOrTerminateWithError($gemeinde_res); $gemeinde_res = $this->getDataOrTerminateWithError($gemeinde_res);
/* $gemeinde_res = array_map(function ($obj) { /* $gemeinde_res = array_map(function ($obj) {
return $obj->ortschaftsname; return $obj->ortschaftsname;
}, $gemeinde_res); */ }, $gemeinde_res); */
$this->terminateWithSuccess($gemeinde_res); $this->terminateWithSuccess($gemeinde_res);
} }
/** /**
* checks whether a specific userID is a mitarbeiter or not (foreword declaration of the function isMitarbeiter in Mitarbeiter_model.php) * checks whether a specific userID is a mitarbeiter or not (foreword declaration of the function isMitarbeiter in Mitarbeiter_model.php)
* @access public * @access public
@@ -150,23 +160,48 @@ class Profil extends FHCAPI_Controller
public function isMitarbeiter($uid) public function isMitarbeiter($uid)
{ {
if(!$uid) $this->terminateWithError("No uid provided", self::ERROR_TYPE_GENERAL); if (!$uid)
$this->terminateWithError("No uid provided", self::ERROR_TYPE_GENERAL);
$result = $this->MitarbeiterModel->isMitarbeiter($uid); $result = $this->MitarbeiterModel->isMitarbeiter($uid);
if (isError($result)) { if (isError($result)) {
$this->terminateWithError("error when calling Mitarbeiter_model function isMitarbeiter with uid " . $uid, self::ERROR_TYPE_GENERAL); $this->terminateWithError("error when calling Mitarbeiter_model function isMitarbeiter with uid " . $uid, self::ERROR_TYPE_GENERAL);
} }
$result = $this->getDataOrTerminateWithError($result); $result = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result); $this->terminateWithSuccess($result);
} }
// ----------------------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------------------
// Private methods // Private methods
/**
* gets the identifier, phrase, and url for each calendar sync option
* @access private
* @return array array of arrays, where each child array is a sync option
*/
private function getCalendarSyncUrlData()
{
return [
[
"identifier" => "cal_dav",
"labelPhrase" => "profil/calendar_sync_cal_dav",
"url" => APP_ROOT . "webdav/lvplan.php/calendars/" . $this->uid . "/LVPlan-" . $this->uid,
],
[
"identifier" => "cal_dav_principal",
"labelPhrase" => "profil/calendar_sync_cal_dav_principal",
"url" => APP_ROOT . "webdav/lvplan.php/principals/" . $this->uid,
],
[
"identifier" => "i_cal",
"labelPhrase" => "profil/calendar_sync_i_cal",
"url" => APP_ROOT . "webdav/google.php?cal=" . encryptData($this->uid, LVPLAN_CYPHER_KEY) . "&" . microtime(true),
],
];
}
} }
@@ -0,0 +1,64 @@
<?php
/**
* Copyright (C) 2024 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/>.
*/
if (!defined('BASEPATH'))
exit('No direct script access allowed');
class StgOrgLvPlan extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'stgOrgLvPlanViewData' => self::PERM_LOGGED,
]);
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* fetches view data for stg org lv plan
* @access public
*/
public function stgOrgLvPlanViewData()
{
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->StudiengangModel->addOrder('typ');
$this->StudiengangModel->addOrder('kurzbz');
$result = $this->StudiengangModel->loadWhere([
'aktiv' => true
]);
$studiengaenge = $this->getDataOrTerminateWithError($result);
$viewData = array(
'studiengaenge' => $studiengaenge,
);
$this->terminateWithSuccess($viewData);
}
// -----------------------------------------------------------------------------------------------------------------
// Private methods
}
@@ -62,21 +62,36 @@ class Studium extends FHCAPI_Controller
if($this->getDataOrTerminateWithError($this->StudentModel->isStudent(getAuthUID()))){ if($this->getDataOrTerminateWithError($this->StudentModel->isStudent(getAuthUID()))){
$studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz]); $studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz]);
$studentLehrverband = current($this->getDataOrTerminateWithError($studentLehrverband));
//TODO(Manu) check if use Fallback or just comment out all paramschecks?
$student_studiensemester = $studentLehrverband->studiensemester_kurzbz; //add Fallback: if no LehrverbandData of actual semester, get Data of previous one
$student_studiengang = $studentLehrverband->studiengang_kz; if(!hasData($studentLehrverband))
$student_semester = $studentLehrverband->semester; {
$result= $this->StudiensemesterModel->getPreviousFrom($aktuelles_studiensemester->studiensemester_kurzbz);
$data = $this->getDataOrTerminateWithError($result);
$vorheriges_studiensemester = current($data)->studiensemester_kurzbz;
$studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $vorheriges_studiensemester]);
}
$studentLehrverband = current(getData($studentLehrverband));
$student_studienplan = $this->getStudienPlanFromPrestudentStatus(getAuthPersonId())->studienplan_id; $student_studienplan = $this->getStudienPlanFromPrestudentStatus(getAuthPersonId())->studienplan_id;
if(!isset($parameter_studiensemester)) if(!isset($parameter_studiensemester)) {
$parameter_studiensemester = $student_studiensemester; $student_studiensemester = $studentLehrverband->studiensemester_kurzbz;
if(!isset($parameter_studiengang)) $parameter_studiensemester = $student_studiensemester;
$parameter_studiengang = $student_studiengang; }
if(!isset($parameter_semester)) if(!isset($parameter_studiengang)) {
$parameter_semester = $student_semester; $student_studiengang = $studentLehrverband->studiengang_kz;
$parameter_studiengang = $student_studiengang;
}
if(!isset($parameter_semester)) {
$student_semester = $studentLehrverband->semester;
$parameter_semester = $student_semester;
}
if(!isset($parameter_studienplan)) if(!isset($parameter_studienplan))
$parameter_studienplan = $student_studienplan; $parameter_studienplan = $student_studienplan;
} }
if(isset($parameter_studiensemester)){ if(isset($parameter_studiensemester)){
@@ -96,8 +111,7 @@ class Studium extends FHCAPI_Controller
// fetch studiensemester // fetch studiensemester
$allStudienSemester = $this->getDataOrTerminateWithError($this->StudiensemesterModel->load()); $allStudienSemester = $this->getDataOrTerminateWithError($this->StudiensemesterModel->load());
if(isset($parameter_studiensemester) && !empty(array_filter($allStudienSemester, function($studiensemester) use($parameter_studiensemester){ if(isset($parameter_studiensemester) && !empty(array_filter($allStudienSemester, function($studiensemester) use($parameter_studiensemester){
return $studiensemester->studiensemester_kurzbz == $parameter_studiensemester->studiensemester_kurzbz; return $studiensemester->studiensemester_kurzbz == $parameter_studiensemester->studiensemester_kurzbz;
}))){ }))){
@@ -216,6 +230,8 @@ class Studium extends FHCAPI_Controller
$studienplaene = array_map(function($studienplan){ $studienplaene = array_map(function($studienplan){
$orgform = current($this->getDataOrTerminateWithError($this->OrgformModel->loadWhere(["orgform_kurzbz" => $studienplan->orgform_kurzbz]))); $orgform = current($this->getDataOrTerminateWithError($this->OrgformModel->loadWhere(["orgform_kurzbz" => $studienplan->orgform_kurzbz])));
$studienplan->orgform_bezeichnung = $orgform->bezeichnung; $studienplan->orgform_bezeichnung = $orgform->bezeichnung;
// bezeichnung_mehrsprachig
$studienplan->orgform_bezeichnung_english = $orgform->bezeichnung_mehrsprachig[1];
return $studienplan; return $studienplan;
},$studienplaene); },$studienplaene);
return $studienplaene; return $studienplaene;
@@ -40,11 +40,32 @@ class Board extends FHCAPI_Controller
public function list() public function list()
{ {
$this->DashboardModel->addSelect('dashboard_id');
$this->DashboardModel->addSelect('dashboard_kurzbz');
$this->DashboardModel->addSelect('tbl_dashboard.beschreibung');
$this->DashboardModel->addSelect("(
SELECT json_agg(w.*)
FROM dashboard.tbl_widget w
JOIN dashboard.tbl_dashboard_widget dw
USING(widget_id)
WHERE dw.dashboard_id=tbl_dashboard.dashboard_id
) AS \"widgetSetup\"");
$result = $this->DashboardModel->load(); $result = $this->DashboardModel->load();
$data = $this->getDataOrTerminateWithError($result); $data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result); $data = array_map(function ($dashboard) {
$tmpSetups = json_decode($dashboard->widgetSetup);
$tmpSetups = array_map(function ($widget) {
$widget->setup->file = absoluteJsImportUrl($widget->setup->file);
return $widget;
}, $tmpSetups);
$dashboard->widgetSetup = $tmpSetups;
return $dashboard;
}, $data);
$this->terminateWithSuccess($data);
} }
public function create() public function create()
@@ -82,7 +103,7 @@ class Board extends FHCAPI_Controller
$data = $this->getDataOrTerminateWithError($result); $data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result); $this->terminateWithSuccess($data);
} }
public function delete() public function delete()
@@ -116,6 +137,6 @@ class Board extends FHCAPI_Controller
$data = $this->getDataOrTerminateWithError($result); $data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result); $this->terminateWithSuccess($data);
} }
} }
@@ -120,10 +120,7 @@ class Preset extends FHCAPI_Controller
$conf = $this->dashboardlib->getPreset($db, $funktion); $conf = $this->dashboardlib->getPreset($db, $funktion);
if ($conf) { if ($conf) {
$preset = json_decode($conf->preset, true); $preset = json_decode($conf->preset, true);
if (!isset($preset[$funktion]) || !isset($preset[$funktion]['widgets'])) $result[$funktion] = $preset;
$result[$funktion] = [];
else
$result[$funktion] = $preset[$funktion]['widgets'];
} else { } else {
$result[$funktion] = []; $result[$funktion] = [];
} }
@@ -154,7 +151,7 @@ class Preset extends FHCAPI_Controller
$preset_decoded = json_decode($preset->preset, true); $preset_decoded = json_decode($preset->preset, true);
$this->dashboardlib->addWidgetsToWidgets($preset_decoded, $dashboard_kurzbz, $funktion_kurzbz, [$widget]); $preset_decoded[$widget['widgetid']] = $widget;
$preset->preset = json_encode($preset_decoded); $preset->preset = json_encode($preset_decoded);
@@ -186,8 +183,10 @@ class Preset extends FHCAPI_Controller
$preset_decoded = json_decode($preset->preset, true); $preset_decoded = json_decode($preset->preset, true);
if (!$this->dashboardlib->removeWidgetFromWidgets($preset_decoded, $funktion_kurzbz, $widgetid)) if (!isset($preset_decoded[$widgetid]))
show_404(); show_404();
unset($preset_decoded[$widgetid]);
$preset->preset = json_encode($preset_decoded); $preset->preset = json_encode($preset_decoded);
@@ -48,25 +48,9 @@ class User extends FHCAPI_Controller
$uid = $this->authlib->getAuthObj()->username; $uid = $this->authlib->getAuthObj()->username;
/*$mergedconfig = $this->dashboardlib->getMergedConfig($dashboard->dashboard_id, $uid); $mergedconfig = $this->dashboardlib->getMergedUserConfig($dashboard->dashboard_id, $uid);
$this->terminateWithSuccess([ $this->terminateWithSuccess($mergedconfig);
'general' => call_user_func_array(
'array_merge_recursive',
$mergedconfig
)
]);*/
$defaultconfig = $this->dashboardlib->getDefaultConfig($dashboard->dashboard_id);
$userconfig = $this->dashboardlib->getUserConfig($dashboard->dashboard_id, $uid);
$defaultconfig_squashed = $defaultconfig ? call_user_func_array('array_replace_recursive', $defaultconfig) : [];
$userconfig_squashed = $userconfig ? call_user_func_array('array_replace_recursive', $userconfig) : [];
$mergedconfig = array_replace_recursive($defaultconfig_squashed, $userconfig_squashed);
$this->terminateWithSuccess([
DashboardLib::SECTION_IF_FUNKTION_KURZBZ_IS_NULL => $mergedconfig
]);
} }
public function addWidget() public function addWidget()
@@ -86,26 +70,15 @@ class User extends FHCAPI_Controller
if (!isset($widget['widgetid'])) if (!isset($widget['widgetid']))
$widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz); $widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz);
if (isset($widget['source']))
unset($widget['source']);
$override = $this->dashboardlib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid); $override = $this->dashboardlib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
$override_decoded = json_decode($override->override, true); $override_decoded = json_decode($override->override, true);
if (!isset($override_decoded['general']) || !is_array($override_decoded['general'])) $override_decoded[$widget['widgetid']] = $widget;
$override_decoded['general'] = [];
if (!isset($override_decoded['general']['widgets']))
$override_decoded['general']['widgets'] = [];
$override_decoded['general']['widgets'][$widget['widgetid']] = $widget;
// NOTE(chris): remove doubles in other funktionen
foreach ($override_decoded as $funktion => $array) {
if ($funktion == 'general')
continue;
if (isset($array['widgets']) && isset($array['widgets'][$widget['widgetid']]))
unset($override_decoded[$funktion]['widgets'][$widget['widgetid']]);
}
$override->override = json_encode($override_decoded); $override->override = json_encode($override_decoded);
$result = $this->dashboardlib->insertOrUpdateOverride($override); $result = $this->dashboardlib->insertOrUpdateOverride($override);
@@ -135,18 +108,10 @@ class User extends FHCAPI_Controller
$override_decoded = json_decode($override->override, true); $override_decoded = json_decode($override->override, true);
foreach (array_keys($override_decoded) as $k) { if (!isset($override_decoded[$widget_id]))
if (!isset($override_decoded[$k]["widgets"])) { show_404();
unset($override_decoded[$k]);
continue; unset($override_decoded[$widget_id]);
}
if (isset($override_decoded[$k]["widgets"][$widget_id])) {
unset($override_decoded[$k]["widgets"][$widget_id]);
}
if (!$override_decoded[$k]["widgets"]) {
unset($override_decoded[$k]);
}
}
$override->override = json_encode($override_decoded); $override->override = json_encode($override_decoded);
@@ -90,6 +90,15 @@ class Projektarbeit extends FHCAPI_Controller
if (!isset($projektarbeit_id) || !is_numeric($projektarbeit_id)) return $this->terminateWithError('Projektarbeit Id missing', self::ERROR_TYPE_GENERAL); if (!isset($projektarbeit_id) || !is_numeric($projektarbeit_id)) return $this->terminateWithError('Projektarbeit Id missing', self::ERROR_TYPE_GENERAL);
$result = $this->fetchProjektarbeitByID($projektarbeit_id);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess(current($data));
}
private function fetchProjektarbeitById($projektarbeit_id) {
$this->ProjektarbeitModel->resetQuery();
$this->ProjektarbeitModel->addSelect( $this->ProjektarbeitModel->addSelect(
'lehre.tbl_projektarbeit.projektarbeit_id, titel, titel_english, themenbereich, projekttyp_kurzbz, lehrveranstaltung_id, lehreinheit_id, 'lehre.tbl_projektarbeit.projektarbeit_id, titel, titel_english, themenbereich, projekttyp_kurzbz, lehrveranstaltung_id, lehreinheit_id,
firma_id, beginn, ende, gesperrtbis, note, final, freigegeben, tbl_projektarbeit.anmerkung, fa.name AS firma_name' firma_id, beginn, ende, gesperrtbis, note, final, freigegeben, tbl_projektarbeit.anmerkung, fa.name AS firma_name'
@@ -97,13 +106,10 @@ class Projektarbeit extends FHCAPI_Controller
$this->ProjektarbeitModel->addJoin('lehre.tbl_lehreinheit le', 'lehreinheit_id'); $this->ProjektarbeitModel->addJoin('lehre.tbl_lehreinheit le', 'lehreinheit_id');
$this->ProjektarbeitModel->addJoin('lehre.tbl_lehrveranstaltung lv', 'lehrveranstaltung_id'); $this->ProjektarbeitModel->addJoin('lehre.tbl_lehrveranstaltung lv', 'lehrveranstaltung_id');
$this->ProjektarbeitModel->addJoin('public.tbl_firma fa', 'firma_id', 'LEFT'); $this->ProjektarbeitModel->addJoin('public.tbl_firma fa', 'firma_id', 'LEFT');
$result = $this->ProjektarbeitModel->loadWhere( return $this->ProjektarbeitModel->loadWhere(
array('projektarbeit_id' => $projektarbeit_id) array('projektarbeit_id' => $projektarbeit_id)
); );
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess(current($data));
} }
/** /**
@@ -132,7 +138,8 @@ class Projektarbeit extends FHCAPI_Controller
); );
$data = $this->getDataOrTerminateWithError($result); $data = $this->getDataOrTerminateWithError($result);
$data = $this->getDataOrTerminateWithError($this->fetchProjektarbeitById($data));
$this->terminateWithSuccess($data); $this->terminateWithSuccess($data);
} }
@@ -280,10 +287,7 @@ class Projektarbeit extends FHCAPI_Controller
*/ */
public function getNoten() public function getNoten()
{ {
$this->NoteModel->addOrder('notenwert', 'ASC'); $result = $this->NoteModel->getAllActive();
$this->NoteModel->addOrder('bezeichnung', 'ASC');
$result = $this->NoteModel->load();
if (isError($result)) return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); if (isError($result)) return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
@@ -358,7 +358,8 @@ class AbgabetoolJob extends JOB_Controller
foreach($assistenzMap as $assistenz_person_id => $tupelArr) { foreach($assistenzMap as $assistenz_person_id => $tupelArr) {
$abgabenString = '<div style="font-family: Arial, sans-serif; color: #333;">'; $abgabenString = '<div style="font-family: Arial, sans-serif; color: #333;">';
$hasContent = false;
foreach($tupelArr as $tupel) { foreach($tupelArr as $tupel) {
$projektarbeit_id = $tupel[0]; $projektarbeit_id = $tupel[0];
$assistenzRow = $tupel[1]; $assistenzRow = $tupel[1];
@@ -377,6 +378,7 @@ class AbgabetoolJob extends JOB_Controller
if(count($relevantAbgaben) == 0) { if(count($relevantAbgaben) == 0) {
continue; continue;
} }
$hasContent = true;
// Format the Student Name // Format the Student Name
$s = $relevantAbgaben[0]; $s = $relevantAbgaben[0];
@@ -447,7 +449,12 @@ class AbgabetoolJob extends JOB_Controller
} }
$abgabenString .= '</div>'; $abgabenString .= '</div>';
// skip send entirely
if (!$hasContent) {
continue;
}
// done with building the change list, now send it // done with building the change list, now send it
$assistenzRow = $tupelArr[0][1]; $assistenzRow = $tupelArr[0][1];
$anrede = $assistenzRow->anrede; $anrede = $assistenzRow->anrede;
+61 -16
View File
@@ -40,6 +40,13 @@ class AntragJob extends JOB_Controller
*/ */
public function sendStglSammelmail() public function sendStglSammelmail()
{ {
if ($this->config->item('abmeldung_enabled') !== true &&
$this->config->item('abmeldung_stg_enabled') !== true &&
$this->config->item('unterbrechung_enabled') !== true &&
$this->config->item('wiederholung_enabled') !== true)
return $this->logError('Konnte Job nicht starten: Keine der Configs "abmeldung_stg_enabled", "abmeldung_enabled", "unterbrechung_enabled", "wiederholung_enabled" auf true gesetzt');
$this->load->model('person/Person_model', 'PersonModel'); $this->load->model('person/Person_model', 'PersonModel');
$this->logInfo('Start Job sendStglSammelmail'); $this->logInfo('Start Job sendStglSammelmail');
@@ -47,25 +54,38 @@ class AntragJob extends JOB_Controller
$this->load->model('organisation/Studiengang_model', 'StudiengangModel'); $this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->StudierendenantragModel->addJoin('public.tbl_prestudent', 'prestudent_id'); $this->StudierendenantragModel->addJoin('public.tbl_prestudent', 'prestudent_id');
$this->db->group_start();
$this->db->where('typ', Studierendenantrag_model::TYP_ABMELDUNG);
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_CREATED);
$this->db->group_end();
$this->db->or_group_start(); if ($this->config->item('abmeldung_enabled') === true)
$this->db->where('typ', Studierendenantrag_model::TYP_ABMELDUNG_STGL); {
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_CREATED); $this->db->group_start();
$this->db->group_end(); $this->db->where('typ', Studierendenantrag_model::TYP_ABMELDUNG);
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_CREATED);
$this->db->group_end();
}
$this->db->or_group_start(); if ($this->config->item('abmeldung_stg_enabled') === true)
$this->db->where('typ', Studierendenantrag_model::TYP_UNTERBRECHUNG); {
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_CREATED); $this->db->or_group_start();
$this->db->group_end(); $this->db->where('typ', Studierendenantrag_model::TYP_ABMELDUNG_STGL);
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_CREATED);
$this->db->group_end();
}
$this->db->or_group_start(); if ($this->config->item('unterbrechung_enabled') === true)
$this->db->where('typ', Studierendenantrag_model::TYP_WIEDERHOLUNG); {
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_LVSASSIGNED); $this->db->or_group_start();
$this->db->group_end(); $this->db->where('typ', Studierendenantrag_model::TYP_UNTERBRECHUNG);
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_CREATED);
$this->db->group_end();
}
if ($this->config->item('wiederholung_enabled') === true)
{
$this->db->or_group_start();
$this->db->where('typ', Studierendenantrag_model::TYP_WIEDERHOLUNG);
$this->db->where('campus.get_status_studierendenantrag(studierendenantrag_id)', Studierendenantragstatus_model::STATUS_LVSASSIGNED);
$this->db->group_end();
}
$result = $this->StudierendenantragModel->load(); $result = $this->StudierendenantragModel->load();
if(isError($result)) if(isError($result))
@@ -206,6 +226,10 @@ class AntragJob extends JOB_Controller
public function sendReminderWiedereinstieg() public function sendReminderWiedereinstieg()
{ {
$now = new DateTime(); $now = new DateTime();
if ($this->config->item('unterbrechung_enabled') !== true)
return $this->logError('Konnte Job nicht starten: Config "unterbrechung_enabled" nicht auf true gesetzt');
$modifier = $this->config->item('unterbrechung_job_remind_wiedereinstieg_date_modifier'); $modifier = $this->config->item('unterbrechung_job_remind_wiedereinstieg_date_modifier');
if (!$modifier) if (!$modifier)
@@ -363,6 +387,13 @@ class AntragJob extends JOB_Controller
{ {
$this->logInfo('Start Job handleWiederholerDeadline'); $this->logInfo('Start Job handleWiederholerDeadline');
if ($this->config->item('wiederholung_enabled') !== true)
{
$this->logError('Config "wiederholung_enabled" nicht auf true gesetzt');
$this->logInfo('Ende Job handleWiederholerDeadline');
return;
}
$this->load->library('PrestudentLib'); $this->load->library('PrestudentLib');
$insertvon = $this->config->item('antrag_job_systemuser'); $insertvon = $this->config->item('antrag_job_systemuser');
@@ -481,6 +512,13 @@ class AntragJob extends JOB_Controller
{ {
$this->logInfo('Start Job handleAbmeldungenStglDeadline'); $this->logInfo('Start Job handleAbmeldungenStglDeadline');
if ($this->config->item('abmeldung_stg_enabled') !== true)
{
$this->logError('Config "abmeldung_stg_enabled" nicht auf true gesetzt');
$this->logInfo('Ende Job handleAbmeldungenStglDeadline');
return;
}
$insertvon = $this->config->item('antrag_job_systemuser'); $insertvon = $this->config->item('antrag_job_systemuser');
if (!$insertvon) { if (!$insertvon) {
$this->logError('Config "antrag_job_systemuser" nicht gesetzt'); $this->logError('Config "antrag_job_systemuser" nicht gesetzt');
@@ -614,6 +652,13 @@ class AntragJob extends JOB_Controller
{ {
$this->logInfo('Start Job sendAufforderungWiederholer'); $this->logInfo('Start Job sendAufforderungWiederholer');
if ($this->config->item('wiederholung_enabled') !== true)
{
$this->logError('Config "wiederholung_enabled" nicht auf true gesetzt');
$this->logInfo('Ende Job sendAufforderungWiederholer');
return;
}
$modifier_request_1 = $this->config->item('wiederholung_job_request_1_date_modifier'); $modifier_request_1 = $this->config->item('wiederholung_job_request_1_date_modifier');
$modifier_request_2 = $this->config->item('wiederholung_job_request_2_date_modifier'); $modifier_request_2 = $this->config->item('wiederholung_job_request_2_date_modifier');
$modifier_deadline = $this->config->item('wiederholung_job_deadline_date_modifier'); $modifier_deadline = $this->config->item('wiederholung_job_deadline_date_modifier');
@@ -28,7 +28,9 @@ class Studierendenantrag extends FHC_Controller
'studierendenantrag' 'studierendenantrag'
]); ]);
if (strtolower($this->router->method) === 'leitung') $this->load->config('studierendenantrag');
if (strtolower($this->router->method) === 'leitung')
$this->_isAllowed([ $this->_isAllowed([
'leitung' => ['student/studierendenantrag:r', 'student/antragfreigabe:r'] 'leitung' => ['student/studierendenantrag:r', 'student/antragfreigabe:r']
]); ]);
@@ -54,18 +56,27 @@ class Studierendenantrag extends FHC_Controller
'bezeichnungStg' => $antrag->bezeichnung, 'bezeichnungStg' => $antrag->bezeichnung,
'bezeichnungOrgform' => $antrag->orgform 'bezeichnungOrgform' => $antrag->orgform
); );
$result = $this->antraglib->getPrestudentWiederholungsBerechtigt($antrag->prestudent_id);
if (getData($result) == 1)
$prestudentenArr[$antrag->prestudent_id]['allowedNewTypes'][] = 'Wiederholung';
$result = $this->antraglib->getPrestudentUnterbrechungsBerechtigt($antrag->prestudent_id); if ($this->config->item('wiederholung_enabled') === true)
if (getData($result) == 1) {
$prestudentenArr[$antrag->prestudent_id]['allowedNewTypes'][] = 'Unterbrechung'; $result = $this->antraglib->getPrestudentWiederholungsBerechtigt($antrag->prestudent_id);
if (getData($result) == 1)
$prestudentenArr[$antrag->prestudent_id]['allowedNewTypes'][] = 'Wiederholung';
}
$result = $this->antraglib->getPrestudentAbmeldeBerechtigt($antrag->prestudent_id); if ($this->config->item('unterbrechung_enabled') === true)
if (getData($result) == 1) {
$prestudentenArr[$antrag->prestudent_id]['allowedNewTypes'][] = 'Abmeldung'; $result = $this->antraglib->getPrestudentUnterbrechungsBerechtigt($antrag->prestudent_id);
if (getData($result) == 1)
$prestudentenArr[$antrag->prestudent_id]['allowedNewTypes'][] = 'Unterbrechung';
}
if ($this->config->item('abmeldung_enabled') === true)
{
$result = $this->antraglib->getPrestudentAbmeldeBerechtigt($antrag->prestudent_id);
if (getData($result) == 1)
$prestudentenArr[$antrag->prestudent_id]['allowedNewTypes'][] = 'Abmeldung';
}
} }
if ($antrag->studierendenantrag_id == null) if ($antrag->studierendenantrag_id == null)
continue; continue;
@@ -88,45 +99,59 @@ class Studierendenantrag extends FHC_Controller
$this->load->view('lehre/Antrag/Leitung/List', [ $this->load->view('lehre/Antrag/Leitung/List', [
'stgA' => $stgA, 'stgA' => $stgA,
'stgL' => $stgL 'stgL' => $stgL,
'abmeldung_enabled' => $this->config->item('abmeldung_stg_enabled')
]); ]);
} }
public function abmeldung($prestudent_id, $studierendenantrag_id = null) public function abmeldung($prestudent_id, $studierendenantrag_id = null)
{ {
$this->load->view('lehre/Antrag/Create', [ if ($this->config->item('abmeldung_enabled') === true)
'prestudent_id' => $prestudent_id, {
'studierendenantrag_id' => $studierendenantrag_id, $this->load->view('lehre/Antrag/Create', [
'antrag_type' => 'Abmeldung' 'prestudent_id' => $prestudent_id,
]); 'studierendenantrag_id' => $studierendenantrag_id,
'antrag_type' => 'Abmeldung'
]);
}
} }
public function abmeldungstgl($prestudent_id, $studierendenantrag_id = null) public function abmeldungstgl($prestudent_id, $studierendenantrag_id = null)
{ {
$this->load->view('lehre/Antrag/Create', [ if ($this->config->item('abmeldung_stg_enabled') === true)
'prestudent_id' => $prestudent_id, {
'studierendenantrag_id' => $studierendenantrag_id, $this->load->view('lehre/Antrag/Create', [
'antrag_type' => 'AbmeldungStgl' 'prestudent_id' => $prestudent_id,
]); 'studierendenantrag_id' => $studierendenantrag_id,
'antrag_type' => 'AbmeldungStgl'
]);
}
} }
public function unterbrechung($prestudent_id, $studierendenantrag_id = null) public function unterbrechung($prestudent_id, $studierendenantrag_id = null)
{ {
$this->load->view('lehre/Antrag/Create', [ if ($this->config->item('unterbrechung_enabled') === true)
'prestudent_id' => $prestudent_id, {
'studierendenantrag_id' => $studierendenantrag_id, $this->load->view('lehre/Antrag/Create', [
'antrag_type' => 'Unterbrechung' 'prestudent_id' => $prestudent_id,
]); 'studierendenantrag_id' => $studierendenantrag_id,
'antrag_type' => 'Unterbrechung'
]);
}
} }
public function wiederholung($prestudent_id, $studierendenantrag_id = null) public function wiederholung($prestudent_id, $studierendenantrag_id = null)
{ {
$this->load->view('lehre/Antrag/Create', [ if ($this->config->item('wiederholung_enabled') === true)
'prestudent_id' => $prestudent_id, {
'studierendenantrag_id' => $studierendenantrag_id, $this->load->view('lehre/Antrag/Create', [
'antrag_type' => 'Wiederholung' 'prestudent_id' => $prestudent_id,
]); 'studierendenantrag_id' => $studierendenantrag_id,
'antrag_type' => 'Wiederholung'
]);
}
} }
/** /**
+2 -1
View File
@@ -1657,7 +1657,7 @@ class AntragLib
$result = $this->_ci->StudierendenantragModel->loadWithStatusWhere($where); $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere($where);
if (isError($result)) if (isError($result))
return $result; return $result;
if (!hasData($result)) if (!hasData($result))
return error($this->_ci->p->t('studierendenantrag', "error_no_antrag_found", ['id' => $studierendenantrag_id])); return error($this->_ci->p->t('studierendenantrag', "error_no_antrag_found", ['id' => $studierendenantrag_id]));
@@ -1709,6 +1709,7 @@ class AntragLib
$result->statustyp = $antrag->statustyp; $result->statustyp = $antrag->statustyp;
$result->status_insertvon = $antrag->status_insertvon; $result->status_insertvon = $antrag->status_insertvon;
$result->grund = $antrag->grund; $result->grund = $antrag->grund;
$result->status_grund = $antrag->status_grund;
$result->studierendenantrag_id = $antrag->studierendenantrag_id; $result->studierendenantrag_id = $antrag->studierendenantrag_id;
$result->typ = $antrag->typ; $result->typ = $antrag->typ;
$result->datum = $antrag->datum; $result->datum = $antrag->datum;
+53
View File
@@ -263,6 +263,59 @@ class ProfilLib{
$element->mailto = "mailto:" . $element->gruppe_kurzbz . "@" . DOMAIN; $element->mailto = "mailto:" . $element->gruppe_kurzbz . "@" . DOMAIN;
return $element; return $element;
}, $mailverteiler_res); }, $mailverteiler_res);
$this->ci->load->model("crm/Student_model", "StudentModel");
$this->ci->StudentModel->addJoin("tbl_benutzer", "tbl_benutzer.uid = tbl_student.student_uid");
$this->ci->StudentModel->addJoin("tbl_person", "tbl_benutzer.person_id = tbl_person.person_id");
$this->ci->StudentModel->addJoin("tbl_studiengang", "tbl_student.studiengang_kz = tbl_studiengang.studiengang_kz");
$this->ci->StudentModel->addSelect("matr_nr, semester, verband, gruppe, kurzbzlang");
$studentResult = $this->ci->StudentModel->loadWhere(["student_uid" => $uid]);
if (isError($studentResult)) {
return error(getData($studentResult));
}
$studentResultData = getData($studentResult);
$studentData = null;
if (is_array($studentResultData) && count($studentResultData)) {
$studentData = $studentResultData[0];
}
if ($studentData && $studentData->matr_nr) {
$this->ci->load->library("phrasesLib", ["profil"], "phrases");
$standardCourseVerteiler = trim($studentData->kurzbzlang) . "_STD";
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $standardCourseVerteiler,
"gruppe_kurzbz" => $standardCourseVerteiler,
"mailto" => "mailto:" . strtolower($standardCourseVerteiler) . "@" . DOMAIN,
];
$semesterVerteiler = trim($studentData->kurzbzlang) . trim($studentData->semester);
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $semesterVerteiler,
"gruppe_kurzbz" => $semesterVerteiler,
"mailto" => "mailto:" . strtolower($semesterVerteiler) . "@" . DOMAIN,
];
if ($studentData->verband && strlen(trim($studentData->verband))) {
$verbandVerteiler = $semesterVerteiler . trim($studentData->verband);
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $verbandVerteiler,
"gruppe_kurzbz" => $verbandVerteiler,
"mailto" => "mailto:" . strtolower($verbandVerteiler) . "@" . DOMAIN,
];
if ($studentData->gruppe && strlen(trim($studentData->gruppe))) {
$gruppeVerteiler = $verbandVerteiler . trim($studentData->gruppe);
$mailverteiler_res[] = [
"beschreibung" => $this->ci->phrases->t('profil', 'alleStudentenVon') . " " . $gruppeVerteiler,
"gruppe_kurzbz" => $gruppeVerteiler,
"mailto" => "mailto:" . strtolower($gruppeVerteiler) . "@" . DOMAIN,
];
}
}
}
return $mailverteiler_res; return $mailverteiler_res;
} }
+31 -7
View File
@@ -40,13 +40,16 @@ class StundenplanLib
* @return stdClass * @return stdClass
* @access public * @access public
*/ */
public function getEventsUser($start, $end) public function getEventsUser($start, $end, $uid = null)
{ {
$this->_ci =& get_instance(); $this->_ci =& get_instance();
$this->_ci->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); $this->_ci->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
$uid = getAuthUID(); if (!$uid) {
$uid = getAuthUID();
}
if (is_null($uid)) if (is_null($uid))
return error("No UID"); return error("No UID");
@@ -217,7 +220,7 @@ class StundenplanLib
* @param string $ort_kurzbz * @param string $ort_kurzbz
* @return stdClass * @return stdClass
*/ */
public function getReservierungen($start_date, $end_date, $ort_kurzbz = '') public function getReservierungen($start_date, $end_date, $ort_kurzbz = '', $uid = null)
{ {
$this->_ci =& get_instance(); $this->_ci =& get_instance();
@@ -228,14 +231,14 @@ class StundenplanLib
$this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel'); $this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel');
$this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel'); $this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel');
$is_mitarbeiter = getData($this->_ci->MitarbeiterModel->isMitarbeiter(getAuthUID())); $is_mitarbeiter = getData($this->_ci->MitarbeiterModel->isMitarbeiter($uid ?? getAuthUID()));
if ($is_mitarbeiter && empty($ort_kurzbz)) { if ($is_mitarbeiter && empty($ort_kurzbz)) {
// request for personal lvplan show only reservations of logged in user // request for personal lvplan show only reservations of logged in user
$reservierungen = $this->_ci->ReservierungModel->getReservierungenMitarbeiter($start_date, $end_date); $reservierungen = $this->_ci->ReservierungModel->getReservierungenMitarbeiter($start_date, $end_date, $uid);
} else { } else {
// querying the reservierungen // querying the reservierungen
$reservierungen = $this->_ci->ReservierungModel->getReservierungen($start_date, $end_date, $ort_kurzbz); $reservierungen = $this->_ci->ReservierungModel->getReservierungen($start_date, $end_date, $ort_kurzbz, $uid);
} }
if (isError($reservierungen)) if (isError($reservierungen))
@@ -357,7 +360,10 @@ class StundenplanLib
if (isError($ort_content_object)) { if (isError($ort_content_object)) {
return error(getData($ort_content_object)); return error(getData($ort_content_object));
} }
$ort_content_object = getData($ort_content_object)[0]; $ort_content_object_data = getData($ort_content_object);
$ort_content_object = (is_array($ort_content_object_data) && count($ort_content_object_data) > 0)
? $ort_content_object_data[0]
: null;
if($ort_content_object) { if($ort_content_object) {
$item->ort_content_id = $ort_content_object->content_id; $item->ort_content_id = $ort_content_object->content_id;
} }
@@ -445,6 +451,24 @@ class StundenplanLib
return success($ferienEventsFlattened); return success($ferienEventsFlattened);
} }
public function getEventsStgOrg( $start, $end, $stg_kz, $sem, $verband, $gruppe)
{
$this->_ci =& get_instance();
$this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel');
$stundenplan_data = $this->_ci->StundenplanModel->getStundenplanStudiengang($start, $end, $stg_kz, $sem, $verband, $gruppe);
if (isError($stundenplan_data))
return $stundenplan_data;
$stundenplan_data = getData($stundenplan_data) ?? [];
$function_error = $this->expandObjectInformation($stundenplan_data);
if ($function_error)
return $function_error;
return success($stundenplan_data);
}
// start of the private functions ######################################################################################################## // start of the private functions ########################################################################################################
// function used to sort an array of studiensemester strings // function used to sort an array of studiensemester strings
@@ -37,7 +37,9 @@ class DashboardLib
public function getDashboardByKurzbz($dashboard_kurzbz) public function getDashboardByKurzbz($dashboard_kurzbz)
{ {
$result = $this->_ci->DashboardModel->getDashboardByKurzbz($dashboard_kurzbz); $result = $this->_ci->DashboardModel->loadWhere([
'dashboard_kurzbz' => $dashboard_kurzbz
]);
if (hasData($result)) if (hasData($result))
{ {
@@ -47,17 +49,21 @@ class DashboardLib
return null; return null;
} }
public function getMergedConfig($dashboard_id, $uid) public function getMergedUserConfig($dashboard_id, $uid)
{ {
$defaultconfig = $this->getDefaultConfig($dashboard_id); $defaultconfig = $this->getUserBaseConfig($dashboard_id);
$userconfig = $this->getUserConfig($dashboard_id, $uid); $userconfig = $this->getUserOverrideConfig($dashboard_id, $uid);
$mergedconfig = array_replace_recursive($defaultconfig, $userconfig); $sourceconfig = array_map(function ($value) {
return ['source' => $value['source']];
}, $defaultconfig);
$mergedconfig = array_replace_recursive($defaultconfig, $userconfig, $sourceconfig);
return $mergedconfig; return $mergedconfig;
} }
public function getDefaultConfig($dashboard_id) protected function getUserBaseConfig($dashboard_id)
{ {
$funktion_kurzbzs = []; $funktion_kurzbzs = [];
$rights = $this->_ci->permissionlib->getAccessRights(); $rights = $this->_ci->permissionlib->getAccessRights();
@@ -87,7 +93,11 @@ class DashboardLib
$preset = json_decode($presetobj->preset, true); $preset = json_decode($presetobj->preset, true);
if (null !== $preset) if (null !== $preset)
{ {
$defaultconfig = array_replace_recursive($defaultconfig, $preset); $preset = array_map(function ($value) use ($presetobj) {
$value['source'] = $presetobj->funktion_kurzbz ?: self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
return $value;
}, $preset);
$defaultconfig = array_merge_recursive($defaultconfig, $preset);
} }
} }
} }
@@ -95,7 +105,7 @@ class DashboardLib
return $defaultconfig; return $defaultconfig;
} }
public function getUserConfig($dashboard_id, $uid) protected function getUserOverrideConfig($dashboard_id, $uid)
{ {
$res_userconfig = $this->_ci->DashboardOverrideModel->getOverride($dashboard_id, $uid); $res_userconfig = $this->_ci->DashboardOverrideModel->getOverride($dashboard_id, $uid);
@@ -124,7 +134,7 @@ class DashboardLib
$emptyoverride = new stdClass(); $emptyoverride = new stdClass();
$emptyoverride->dashboard_id = $dashboard->dashboard_id; $emptyoverride->dashboard_id = $dashboard->dashboard_id;
$emptyoverride->uid = $uid; $emptyoverride->uid = $uid;
$emptyoverride->override = '{"' . self::USEROVERRIDE_SECTION . '": {"widgets":{}}, "custom": { "widgets" : {}}}'; $emptyoverride->override = '[]';
return $emptyoverride; return $emptyoverride;
} }
@@ -143,8 +153,7 @@ class DashboardLib
$emptypreset = new stdClass(); $emptypreset = new stdClass();
$emptypreset->dashboard_id = $dashboard->dashboard_id; $emptypreset->dashboard_id = $dashboard->dashboard_id;
$emptypreset->funktion_kurzbz = $funktion_kurzbz; $emptypreset->funktion_kurzbz = $funktion_kurzbz;
$section = ($funktion_kurzbz !== null) ? $funktion_kurzbz : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; $emptypreset->preset = '[]';
$emptypreset->preset = '{"' . $section . '": { "widgets" : {}},"custom": { "widgets" : {}}}';
return $emptypreset; return $emptypreset;
} }
@@ -209,44 +218,4 @@ class DashboardLib
return $result; return $result;
} }
public function addWidgetsToWidgets(&$widgets, $dashboard_kurzbz, $section, $addwigets)
{
foreach ($addwigets as $widget)
{
if(!isset($widget['widgetid']))
{
$widget['widgetid'] = $this->generateWidgetId($dashboard_kurzbz);
}
$this->addWidgetToWidgets($widgets, $section, $widget, $widget['widgetid']);
}
}
public function addWidgetToWidgets(&$widgets, $section, $widget, $widgetid)
{
$section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
if (!isset($widgets[$section]) || !isset($widgets[$section]["widgets"]) || !is_array($widgets[$section]))
{
$widgets[$section] = array();
$widgets[$section]["widgets"] = array();
}
$widgets[$section]["widgets"][$widgetid] = $widget;
}
public function removeWidgetFromWidgets(&$widgets, $section, $widgetid)
{
$section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
if (isset($widgets[$section]) && isset($widgets[$section]["widgets"][$widgetid]))
{
unset($widgets[$section]["widgets"][$widgetid]);
if(empty($widgets[$section]["widgets"]) && $section !== self::USEROVERRIDE_SECTION) {
unset($widgets[$section]);
}
return true;
}
else {
return false;
}
}
} }
+3 -3
View File
@@ -234,9 +234,9 @@ class Content_model extends DB_Model
FROM FROM
campus.tbl_content c1 campus.tbl_content c1
LEFT JOIN LEFT JOIN
campus.tbl_contentsprache s1 ON c1.content_id=s1.content_id AND s1.sprache=? campus.tbl_contentsprache s1 ON c1.content_id=s1.content_id AND s1.sprache=? AND sichtbar=true
WHERE WHERE
sichtbar=true c1.aktiv = true
) s2 ) s2
LEFT JOIN LEFT JOIN
campus.tbl_contentsprache s3 USING(content_id, sprache) campus.tbl_contentsprache s3 USING(content_id, sprache)
@@ -277,7 +277,7 @@ class Content_model extends DB_Model
JOIN JOIN
campus.tbl_contentsprache s USING(contentsprache_id) campus.tbl_contentsprache s USING(contentsprache_id)
LEFT JOIN LEFT JOIN
campus.tbl_contentchild k ON(m.content_id=k.content_id) campus.tbl_contentchild k ON(m.content_id=k.content_id) and c.aktiv = true
WHERE EXISTS ( WHERE EXISTS (
SELECT 1 SELECT 1
FROM campus.tbl_contentgruppe FROM campus.tbl_contentgruppe
@@ -11,8 +11,4 @@ class Bookmark_model extends DB_Model
$this->dbTable = 'dashboard.tbl_bookmark'; $this->dbTable = 'dashboard.tbl_bookmark';
$this->pk = 'bookmark_id'; $this->pk = 'bookmark_id';
} }
} }
@@ -11,15 +11,4 @@ class Dashboard_model extends DB_Model
$this->dbTable = 'dashboard.tbl_dashboard'; $this->dbTable = 'dashboard.tbl_dashboard';
$this->pk = 'dashboard_id'; $this->pk = 'dashboard_id';
} }
/**
* Get Dashboard by kurzbz.
* @param string dashboard_kurzbz
* @return array
*/
public function getDashboardByKurzbz($dashboard_kurzbz)
{
return $this->loadWhere(array('dashboard_kurzbz' => $dashboard_kurzbz));
}
} }
@@ -187,7 +187,10 @@ class Projektarbeit_model extends DB_Model
campus.tbl_paabgabetyp.bezeichnung, campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabetyp.benotbar, campus.tbl_paabgabetyp.benotbar,
campus.tbl_paabgabe.abgabedatum, campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon campus.tbl_paabgabe.insertvon,
campus.tbl_paabgabe.insertamum,
campus.tbl_paabgabe.updatevon,
campus.tbl_paabgabe.updateamum
FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz) FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz)
WHERE campus.tbl_paabgabe.projektarbeit_id = ? WHERE campus.tbl_paabgabe.projektarbeit_id = ?
ORDER BY campus.tbl_paabgabe.datum"; ORDER BY campus.tbl_paabgabe.datum";
@@ -207,7 +210,18 @@ class Projektarbeit_model extends DB_Model
campus.tbl_paabgabetyp.paabgabetyp_kurzbz, campus.tbl_paabgabetyp.paabgabetyp_kurzbz,
campus.tbl_paabgabetyp.bezeichnung, campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabe.abgabedatum, campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon campus.tbl_paabgabe.insertvon,
campus.tbl_paabgabe.updatevon,
campus.tbl_paabgabe.insertamum,
campus.tbl_paabgabe.updateamum,
(SELECT p.vorname || ' ' || p.nachname
FROM public.tbl_benutzer b
JOIN public.tbl_person p USING(person_id)
WHERE b.uid = campus.tbl_paabgabe.insertvon) AS insertvon_fullname,
(SELECT p.vorname || ' ' || p.nachname
FROM public.tbl_benutzer b
JOIN public.tbl_person p USING(person_id)
WHERE b.uid = campus.tbl_paabgabe.updatevon) AS updatevon_fullname
FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz) FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz)
WHERE campus.tbl_paabgabe.projektarbeit_id IN ? WHERE campus.tbl_paabgabe.projektarbeit_id IN ?
ORDER BY campus.tbl_paabgabe.datum"; ORDER BY campus.tbl_paabgabe.datum";
@@ -240,7 +254,8 @@ class Projektarbeit_model extends DB_Model
LEFT JOIN public.tbl_benutzer ben USING (person_id) LEFT JOIN public.tbl_benutzer ben USING (person_id)
LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid
WHERE (ben.aktiv OR ben.aktiv IS NULL) WHERE (ben.aktiv OR ben.aktiv IS NULL)
AND projektarbeit_id = ?"; AND projektarbeit_id = ?
AND lehre.tbl_projektbetreuer.betreuerart_kurzbz = ANY('{Begutachter, Betreuer, Erstbetreuer,Erstbegutachter,Senatsvorsitz}')";
return $this->execReadOnlyQuery($qry, [$projektarbeit_id]); return $this->execReadOnlyQuery($qry, [$projektarbeit_id]);
} }
@@ -294,34 +309,142 @@ class Projektarbeit_model extends DB_Model
* Get a List of Projektarbeiten of a mitarbeiter with zuordnung * Get a List of Projektarbeiten of a mitarbeiter with zuordnung
* used by the mitarbeiter cis4 abgabetool. * used by the mitarbeiter cis4 abgabetool.
*/ */
public function getMitarbeiterProjektarbeiten($uid, $showAll){ public function getMitarbeiterProjektarbeiten($person_id, $showAll){
$qry = "SELECT $qry = "WITH secondary_betreuer AS (
* SELECT DISTINCT ON (pb.projektarbeit_id)
FROM pb.projektarbeit_id,
(SELECT tbl_person.vorname, tbl_person.nachname, tbl_studiengang.typ, tbl_studiengang.kurzbz, pb.person_id AS zweitbetreuer_person_id,
tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung, tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id, tbl_projektarbeit.note, b.uid AS zweitbetreuer_benutzer_uid,
tbl_projektbetreuer.person_id as betreuer_person_id, tbl_projektbetreuer.betreuerart_kurzbz, tbl_betreuerart.beschreibung AS betreuerart_beschreibung, pb.betreuerart_kurzbz AS zweitbetreuer_betreuerart_kurzbz,
tbl_benutzer.uid, tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz, public.tbl_student.student_uid ba.beschreibung AS zweitbetreuer_betreuerart_beschreibung,
FROM lehre.tbl_projektarbeit p.titelpre AS zweitbetreuer_titelpre,
LEFT JOIN lehre.tbl_projektbetreuer using(projektarbeit_id) p.vorname AS zweitbetreuer_vorname,
LEFT JOIN lehre.tbl_betreuerart using(betreuerart_kurzbz) p.nachname AS zweitbetreuer_nachname,
LEFT JOIN public.tbl_benutzer on(uid=student_uid) p.titelpost AS zweitbetreuer_titelpost,
LEFT JOIN public.tbl_student on(public.tbl_benutzer.uid=public.tbl_student.student_uid) trim(
LEFT JOIN public.tbl_person on(tbl_benutzer.person_id=tbl_person.person_id) COALESCE(p.titelpre, '') || ' ' ||
LEFT JOIN lehre.tbl_lehreinheit using(lehreinheit_id) COALESCE(p.vorname, '') || ' ' ||
LEFT JOIN lehre.tbl_lehrveranstaltung using(lehrveranstaltung_id) COALESCE(p.nachname, '') || ' ' ||
LEFT JOIN public.tbl_studiengang on(lehre.tbl_lehrveranstaltung.studiengang_kz=public.tbl_studiengang.studiengang_kz) COALESCE(p.titelpost, '')
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz) ) AS zweitbetreuer_full_name
WHERE (projekttyp_kurzbz='Bachelor' OR projekttyp_kurzbz='Diplom') FROM lehre.tbl_projektbetreuer pb
AND tbl_projektbetreuer.person_id IN (SELECT person_id FROM public.tbl_benutzer JOIN public.tbl_person p ON p.person_id = pb.person_id
WHERE public.tbl_benutzer.person_id=lehre.tbl_projektbetreuer.person_id LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
AND public.tbl_benutzer.uid= ? ) LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
".($showAll?'':' AND public.tbl_benutzer.aktiv AND lehre.tbl_projektarbeit.note IS NULL ')." WHERE pb.betreuerart_kurzbz = ANY('{Zweitbetreuer,Zweitbegutachter,Senatsmitglied}')
ORDER BY pb.projektarbeit_id
),
primary_betreuer AS (
SELECT DISTINCT ON (pb.projektarbeit_id)
pb.projektarbeit_id,
pb.person_id AS betreuer_person_id,
pb.betreuerart_kurzbz AS betreuer_betreuerart_kurzbz,
ba.beschreibung AS betreuer_betreuerart_beschreibung,
p.titelpre AS betreuer_titelpre,
p.vorname AS betreuer_vorname,
p.nachname AS betreuer_nachname,
p.titelpost AS betreuer_titelpost,
trim(
COALESCE(p.titelpre, '') || ' ' ||
COALESCE(p.vorname, '') || ' ' ||
COALESCE(p.nachname, '') || ' ' ||
COALESCE(p.titelpost, '')
) AS betreuer_full_name
FROM lehre.tbl_projektbetreuer pb
JOIN public.tbl_person p ON p.person_id = pb.person_id
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
WHERE pb.betreuerart_kurzbz = ANY('{Begutachter, Betreuer, Erstbetreuer,Erstbegutachter,Senatsvorsitz}')
ORDER BY pb.projektarbeit_id
)
SELECT
*
FROM
(SELECT DISTINCT ON (tbl_projektarbeit.projektarbeit_id)
student_person.vorname, student_person.nachname,
tbl_studiengang.typ, tbl_studiengang.kurzbz,
tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung,
tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id, tbl_projektarbeit.note,
student_benutzer.uid, tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz, public.tbl_student.student_uid,
lehre.tbl_projektarbeit.sprache,
lehre.tbl_projektarbeit.seitenanzahl,
lehre.tbl_projektarbeit.kontrollschlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter_en,
lehre.tbl_projektarbeit.abstract,
lehre.tbl_projektarbeit.abstract_en,
lehre.tbl_projektarbeit.insertamum,
(
SELECT orgform_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = (
SELECT prestudent_id FROM public.tbl_student
WHERE student_uid = student_benutzer.uid LIMIT 1
)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS organisationsform,
(
SELECT COALESCE(tbl_studienplan.orgform_kurzbz,
tbl_prestudentstatus.orgform_kurzbz,
tbl_studiengang.orgform_kurzbz)
FROM public.tbl_prestudent
JOIN public.tbl_prestudentstatus USING (prestudent_id)
JOIN public.tbl_studiensemester USING (studiensemester_kurzbz)
JOIN public.tbl_studiengang USING (studiengang_kz)
LEFT JOIN lehre.tbl_studienplan USING (studienplan_id)
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY tbl_prestudentstatus.datum DESC
LIMIT 1
) AS orgform,
(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS studienstatus,
pb.betreuer_person_id,
pb.betreuer_betreuerart_kurzbz,
pb.betreuer_betreuerart_beschreibung,
pb.betreuer_titelpre,
pb.betreuer_vorname,
pb.betreuer_nachname,
pb.betreuer_titelpost,
pb.betreuer_full_name,
sb.zweitbetreuer_person_id,
sb.zweitbetreuer_benutzer_uid,
sb.zweitbetreuer_betreuerart_kurzbz,
sb.zweitbetreuer_betreuerart_beschreibung,
sb.zweitbetreuer_titelpre,
sb.zweitbetreuer_vorname,
sb.zweitbetreuer_nachname,
sb.zweitbetreuer_titelpost,
sb.zweitbetreuer_full_name
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON student_benutzer.uid = lehre.tbl_projektarbeit.student_uid
LEFT JOIN public.tbl_person student_person ON student_benutzer.person_id = student_person.person_id
LEFT JOIN public.tbl_student ON student_benutzer.uid = public.tbl_student.student_uid
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN primary_betreuer pb ON pb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
WHERE (projekttyp_kurzbz='Bachelor' OR projekttyp_kurzbz='Diplom')
AND (pb.betreuer_person_id = ? OR sb.zweitbetreuer_person_id = ?)
".($showAll?'':' AND student_benutzer.aktiv AND lehre.tbl_projektarbeit.note IS NULL ')."
AND betreuerart_kurzbz IN ('Betreuer', 'Begutachter', 'Erstbegutachter', 'Zweitbegutachter', 'Erstbetreuer', 'Senatsvorsitz', 'Senatsmitglied') AND betreuerart_kurzbz IN ('Betreuer', 'Begutachter', 'Erstbegutachter', 'Zweitbegutachter', 'Erstbetreuer', 'Senatsvorsitz', 'Senatsmitglied')
ORDER BY tbl_projektarbeit.projektarbeit_id, betreuerart_kurzbz desc) as xy ORDER BY tbl_projektarbeit.projektarbeit_id, betreuerart_kurzbz desc) as xy
ORDER BY nachname;"; ORDER BY nachname;";
return $this->execReadOnlyQuery($qry, array($uid)); return $this->execReadOnlyQuery($qry, array($person_id, $person_id));
} }
/** /**
@@ -341,176 +464,257 @@ class Projektarbeit_model extends DB_Model
public function getProjektarbeitenForStudiengang($studiengang_kz, $benotet) { public function getProjektarbeitenForStudiengang($studiengang_kz, $benotet) {
$new_qry = "SELECT DISTINCT ON(tmp.projektarbeit_id) *, campus.get_betreuer_details(tmp.zweitbetreuer_person_id) as zweitbetreuer_full_name, campus.get_betreuer_details(tmp.betreuer_person_id) as erstbetreuer_full_name $new_qry = "WITH secondary_betreuer AS (
FROM( SELECT DISTINCT ON (pb.projektarbeit_id)
SELECT pb.projektarbeit_id,
DISTINCT ON(tbl_projektarbeit.projektarbeit_id) pb.person_id AS zweitbetreuer_person_id,
tbl_projektarbeit.projekttyp_kurzbz, pb.betreuerart_kurzbz AS zweitbetreuer_betreuerart_kurzbz,
tbl_projektarbeit.titel, ba.beschreibung AS zweitbetreuer_betreuerart_beschreibung,
tbl_projektarbeit.projektarbeit_id, p.titelpre AS zweitbetreuer_titelpre,
tbl_studiengang.typ, tbl_studiengang.kurzbz, p.vorname AS zweitbetreuer_vorname,
student_benutzer.uid as student_uid, p.nachname AS zweitbetreuer_nachname,
student_person.vorname as student_vorname, p.titelpost AS zweitbetreuer_titelpost,
student_person.nachname as student_nachname, trim(
tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz, COALESCE(p.titelpre, '') || ' ' ||
betreuer_benutzer.uid as betreuer_benutzer_uid, COALESCE(p.vorname, '') || ' ' ||
betreuer_person.titelpre as betreuer_titelpre, COALESCE(p.nachname, '') || ' ' ||
betreuer_person.vorname as betreuer_vorname, COALESCE(p.titelpost, '')
betreuer_person.nachname as betreuer_nachname, ) AS zweitbetreuer_full_name
betreuer_person.titelpost as betreuer_titelpost, FROM lehre.tbl_projektbetreuer pb
lehre.tbl_projektbetreuer.betreuerart_kurzbz as betreuerart, JOIN public.tbl_person p ON p.person_id = pb.person_id
lehre.tbl_projektbetreuer.person_id as betreuer_person_id, LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
lehre.tbl_projektarbeit.sprache as sprache, LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
lehre.tbl_projektarbeit.seitenanzahl as seitenanzahl, WHERE pb.betreuerart_kurzbz = ANY('{Zweitbetreuer,Zweitbegutachter,Senatsmitglied}')
lehre.tbl_projektarbeit.kontrollschlagwoerter as kontrollschlagwoerter, ORDER BY pb.projektarbeit_id -- DISTINCT ON needs this to be deterministic
lehre.tbl_projektarbeit.schlagwoerter as schlagwoerter, )
lehre.tbl_projektarbeit.schlagwoerter_en as schlagwoerter_en,
lehre.tbl_projektarbeit.abstract as abstract,
lehre.tbl_projektarbeit.abstract_en as abstract_en,
lehre.tbl_projektarbeit.insertamum as insertamum,
lehre.tbl_projektarbeit.note as note,
(
SELECT orgform_kurzbz
FROM tbl_prestudentstatus
WHERE prestudent_id = (SELECT prestudent_id
FROM tbl_student
WHERE student_uid = student_benutzer.uid
LIMIT 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
)
as organisationsform,
(
SELECT person_id
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
AS zweitbetreuer_person_id,
(
SELECT betreuerart_kurzbz
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
AS zweitbetreuer_betreuerart_kurzbz,
(
SELECT tbl_betreuerart.beschreibung
FROM lehre.tbl_projektbetreuer
JOIN lehre.tbl_betreuerart USING (betreuerart_kurzbz)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
AS zweitbetreuer_betreuerart_beschreibung,
(
SELECT trim(COALESCE(titelpre, '') || ' ' || COALESCE(vorname, '') || ' ' || COALESCE(nachname, '') || ' ' ||
COALESCE(titelpost, ''))
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer ON (lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_benutzer ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
as zweitbetreuer_full_name,
(
SELECT titelpre
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer ON (lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_benutzer ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
as zweitbetreuer_titelpre,
(
SELECT vorname
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer ON (lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_benutzer ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
as zweitbetreuer_vorname,
(
SELECT nachname
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer ON (lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_benutzer ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
as zweitbetreuer_nachname,
(
SELECT titelpost
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer ON (lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_benutzer ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
as zweitbetreuer_titelpost,
(
SELECT
COALESCE(tbl_studienplan.orgform_kurzbz,
tbl_prestudentstatus.orgform_kurzbz, tbl_studiengang.orgform_kurzbz) as
orgform
FROM
public.tbl_prestudent
JOIN public.tbl_prestudentstatus USING(prestudent_id)
JOIN public.tbl_studiensemester USING(studiensemester_kurzbz)
JOIN public.tbl_studiengang USING(studiengang_kz)
LEFT JOIN lehre.tbl_studienplan USING(studienplan_id)
WHERE
prestudent_id=tbl_student.prestudent_id
ORDER BY tbl_prestudentstatus.datum DESC LIMIT 1
) as orgform,
(SELECT status_kurzbz FROM public.tbl_prestudentstatus
WHERE prestudent_id=tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as studienstatus
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON (student_benutzer.uid = lehre.tbl_projektarbeit.student_uid)
LEFT JOIN public.tbl_person student_person ON (student_benutzer.person_id = student_person.person_id)
LEFT JOIN public.tbl_student on(student_benutzer.uid = public.tbl_student.student_uid)
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON (public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz)
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN public.tbl_person betreuer_person ON (betreuer_person.person_id = lehre.tbl_projektbetreuer.person_id)
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON (betreuer_person.person_id = betreuer_benutzer.person_id)
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv AND (
lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbegutachter'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Begutachter'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Betreuer'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbetreuer'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Senatsvorsitz'
)
AND public.tbl_studiengang.studiengang_kz = ?";
if($benotet == 0) { SELECT DISTINCT ON (tmp.projektarbeit_id)
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NULL "; *,
} else if ($benotet == 1) { campus.get_betreuer_details(tmp.zweitbetreuer_person_id) AS zweitbetreuer_full_name,
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NOT NULL "; campus.get_betreuer_details(tmp.betreuer_person_id) AS erstbetreuer_full_name
} FROM (
SELECT DISTINCT ON (tbl_projektarbeit.projektarbeit_id)
$new_qry .= " ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC tbl_projektarbeit.projekttyp_kurzbz,
tbl_projektarbeit.titel,
tbl_projektarbeit.projektarbeit_id,
tbl_studiengang.typ,
tbl_studiengang.kurzbz,
student_benutzer.uid AS student_uid,
student_person.vorname AS student_vorname,
student_person.nachname AS student_nachname,
public.tbl_student.matrikelnr,
tbl_lehreinheit.studiensemester_kurzbz,
betreuer_benutzer.uid AS betreuer_benutzer_uid,
betreuer_person.titelpre AS betreuer_titelpre,
betreuer_person.vorname AS betreuer_vorname,
betreuer_person.nachname AS betreuer_nachname,
betreuer_person.titelpost AS betreuer_titelpost,
lehre.tbl_projektbetreuer.betreuerart_kurzbz AS betreuerart,
lehre.tbl_projektbetreuer.person_id AS betreuer_person_id,
lehre.tbl_projektarbeit.sprache,
lehre.tbl_projektarbeit.seitenanzahl,
lehre.tbl_projektarbeit.kontrollschlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter_en,
lehre.tbl_projektarbeit.abstract,
lehre.tbl_projektarbeit.abstract_en,
lehre.tbl_projektarbeit.insertamum,
lehre.tbl_projektarbeit.note,
sb.zweitbetreuer_person_id,
sb.zweitbetreuer_betreuerart_kurzbz,
sb.zweitbetreuer_betreuerart_beschreibung,
sb.zweitbetreuer_full_name,
sb.zweitbetreuer_titelpre,
sb.zweitbetreuer_vorname,
sb.zweitbetreuer_nachname,
sb.zweitbetreuer_titelpost,
(
SELECT orgform_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = (
SELECT prestudent_id FROM public.tbl_student
WHERE student_uid = student_benutzer.uid LIMIT 1
)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS organisationsform,
(
SELECT COALESCE(tbl_studienplan.orgform_kurzbz,
tbl_prestudentstatus.orgform_kurzbz,
tbl_studiengang.orgform_kurzbz)
FROM public.tbl_prestudent
JOIN public.tbl_prestudentstatus USING (prestudent_id)
JOIN public.tbl_studiensemester USING (studiensemester_kurzbz)
JOIN public.tbl_studiengang USING (studiengang_kz)
LEFT JOIN lehre.tbl_studienplan USING (studienplan_id)
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY tbl_prestudentstatus.datum DESC
LIMIT 1
) AS orgform,
(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS studienstatus
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON student_benutzer.uid = lehre.tbl_projektarbeit.student_uid
LEFT JOIN public.tbl_person student_person ON student_benutzer.person_id = student_person.person_id
LEFT JOIN public.tbl_student ON student_benutzer.uid = public.tbl_student.student_uid
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN public.tbl_person betreuer_person ON betreuer_person.person_id = lehre.tbl_projektbetreuer.person_id
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON betreuer_person.person_id = betreuer_benutzer.person_id
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv
AND lehre.tbl_projektbetreuer.betreuerart_kurzbz IN (
'Erstbegutachter', 'Begutachter', 'Betreuer', 'Erstbetreuer', 'Senatsvorsitz'
)
AND public.tbl_studiengang.studiengang_kz = ?";
if($benotet == 0) {
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NULL ";
} else if ($benotet == 1) {
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NOT NULL ";
}
$new_qry .= " ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
) as tmp"; ) as tmp";
return $this->execReadOnlyQuery($new_qry, array($studiengang_kz)); return $this->execReadOnlyQuery($new_qry, array($studiengang_kz));
} }
public function getProjektarbeitenForStudent($student_uid) {
$new_qry = "WITH secondary_betreuer AS (
SELECT DISTINCT ON (pb.projektarbeit_id)
pb.projektarbeit_id,
pb.person_id AS zweitbetreuer_person_id,
pb.betreuerart_kurzbz AS zweitbetreuer_betreuerart_kurzbz,
ba.beschreibung AS zweitbetreuer_betreuerart_beschreibung,
p.titelpre AS zweitbetreuer_titelpre,
p.vorname AS zweitbetreuer_vorname,
p.nachname AS zweitbetreuer_nachname,
p.titelpost AS zweitbetreuer_titelpost,
trim(
COALESCE(p.titelpre, '') || ' ' ||
COALESCE(p.vorname, '') || ' ' ||
COALESCE(p.nachname, '') || ' ' ||
COALESCE(p.titelpost, '')
) AS zweitbetreuer_full_name
FROM lehre.tbl_projektbetreuer pb
JOIN public.tbl_person p ON p.person_id = pb.person_id
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
WHERE pb.betreuerart_kurzbz = ANY('{Zweitbetreuer,Zweitbegutachter,Senatsmitglied}')
ORDER BY pb.projektarbeit_id -- DISTINCT ON needs this to be deterministic
)
SELECT DISTINCT ON (tmp.projektarbeit_id)
*,
campus.get_betreuer_details(tmp.zweitbetreuer_person_id) AS zweitbetreuer_full_name,
campus.get_betreuer_details(tmp.betreuer_person_id) AS erstbetreuer_full_name
FROM (
SELECT DISTINCT ON (tbl_projektarbeit.projektarbeit_id)
tbl_projektarbeit.projekttyp_kurzbz,
tbl_projektarbeit.titel,
tbl_projektarbeit.projektarbeit_id,
tbl_studiengang.typ,
tbl_studiengang.kurzbz,
student_benutzer.uid AS student_uid,
student_person.vorname AS student_vorname,
student_person.nachname AS student_nachname,
public.tbl_student.matrikelnr,
tbl_lehreinheit.studiensemester_kurzbz,
betreuer_benutzer.uid AS betreuer_benutzer_uid,
betreuer_person.titelpre AS betreuer_titelpre,
betreuer_person.vorname AS betreuer_vorname,
betreuer_person.nachname AS betreuer_nachname,
betreuer_person.titelpost AS betreuer_titelpost,
lehre.tbl_projektbetreuer.betreuerart_kurzbz AS betreuerart,
lehre.tbl_projektbetreuer.person_id AS betreuer_person_id,
lehre.tbl_projektarbeit.sprache,
lehre.tbl_projektarbeit.seitenanzahl,
lehre.tbl_projektarbeit.kontrollschlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter_en,
lehre.tbl_projektarbeit.abstract,
lehre.tbl_projektarbeit.abstract_en,
lehre.tbl_projektarbeit.insertamum,
lehre.tbl_projektarbeit.note,
sb.zweitbetreuer_person_id,
sb.zweitbetreuer_betreuerart_kurzbz,
sb.zweitbetreuer_betreuerart_beschreibung,
sb.zweitbetreuer_full_name,
sb.zweitbetreuer_titelpre,
sb.zweitbetreuer_vorname,
sb.zweitbetreuer_nachname,
sb.zweitbetreuer_titelpost,
(
SELECT orgform_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = (
SELECT prestudent_id FROM public.tbl_student
WHERE student_uid = student_benutzer.uid LIMIT 1
)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS organisationsform,
(
SELECT COALESCE(tbl_studienplan.orgform_kurzbz,
tbl_prestudentstatus.orgform_kurzbz,
tbl_studiengang.orgform_kurzbz)
FROM public.tbl_prestudent
JOIN public.tbl_prestudentstatus USING (prestudent_id)
JOIN public.tbl_studiensemester USING (studiensemester_kurzbz)
JOIN public.tbl_studiengang USING (studiengang_kz)
LEFT JOIN lehre.tbl_studienplan USING (studienplan_id)
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY tbl_prestudentstatus.datum DESC
LIMIT 1
) AS orgform,
(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS studienstatus
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON student_benutzer.uid = lehre.tbl_projektarbeit.student_uid
LEFT JOIN public.tbl_person student_person ON student_benutzer.person_id = student_person.person_id
LEFT JOIN public.tbl_student ON student_benutzer.uid = public.tbl_student.student_uid
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN public.tbl_person betreuer_person ON betreuer_person.person_id = lehre.tbl_projektbetreuer.person_id
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON betreuer_person.person_id = betreuer_benutzer.person_id
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv
AND lehre.tbl_projektbetreuer.betreuerart_kurzbz IN (
'Erstbegutachter', 'Begutachter', 'Betreuer', 'Erstbetreuer', 'Senatsvorsitz'
)
AND public.tbl_student.student_uid = ?
ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
) as tmp";
return $this->execReadOnlyQuery($new_qry, array($student_uid));
}
/** /**
* *
@@ -149,6 +149,7 @@ class Studierendenantrag_model extends DB_Model
$this->addSelect($this->dbTable . '.grund AS grund'); $this->addSelect($this->dbTable . '.grund AS grund');
$this->addSelect('s.studierendenantrag_statustyp_kurzbz status'); $this->addSelect('s.studierendenantrag_statustyp_kurzbz status');
$this->addSelect('s.insertvon status_insertvon'); $this->addSelect('s.insertvon status_insertvon');
$this->addSelect('s.grund AS status_grund');
$this->addSelect('t.bezeichnung[(' . $lang . ')] statustyp'); $this->addSelect('t.bezeichnung[(' . $lang . ')] statustyp');
$this->addSelect('p.unruly AS unruly'); $this->addSelect('p.unruly AS unruly');
$this->addSelect($this->dbTable . '.insertamum AS insertamum'); $this->addSelect($this->dbTable . '.insertamum AS insertamum');
@@ -269,6 +270,7 @@ class Studierendenantrag_model extends DB_Model
$this->addSelect($this->dbTable . '.grund'); $this->addSelect($this->dbTable . '.grund');
$this->addSelect($this->dbTable . '.dms_id'); $this->addSelect($this->dbTable . '.dms_id');
$this->addSelect('s.insertvon AS status_insertvon'); $this->addSelect('s.insertvon AS status_insertvon');
$this->addSelect('s.grund AS status_grund');
$this->addSelect( $this->addSelect(
"(SELECT count(1) FROM campus.tbl_studierendenantrag_status WHERE studierendenantrag_id = " . "(SELECT count(1) FROM campus.tbl_studierendenantrag_status WHERE studierendenantrag_id = " .
$this->dbTable . $this->dbTable .
@@ -50,7 +50,7 @@ class Studierendenantragstatus_model extends DB_Model
$this->addSelect('bezeichnung[(' . $lang . ')] AS typ'); $this->addSelect('bezeichnung[(' . $lang . ')] AS typ');
$this->addJoin('campus.tbl_studierendenantrag_statustyp', 'studierendenantrag_statustyp_kurzbz'); $this->addJoin('campus.tbl_studierendenantrag_statustyp', 'studierendenantrag_statustyp_kurzbz');
$this->addOrder($this->dbTable. '.insertamum', 'DESC');
return $this->loadWhere($where); return $this->loadWhere($where);
} }
@@ -122,6 +122,47 @@ class Studiensemester_model extends DB_Model
return $this->execQuery($query, array($studiensemester_kurzbz, $limit)); return $this->execQuery($query, array($studiensemester_kurzbz, $limit));
} }
/**
* Returns the current study semester and the previous semesters of the same type (SS/WS).
*
* Example:
* - SS2026, limit 3: SS2026, SS2025, SS2024
* - WS2025, limit 2: WS2025, WS2024
*
* @param $studiensemester_kurzbz
* @param $limit
*/
public function getPreviousSameSemesterFrom($studiensemester_kurzbz, $limit = 1)
{
$qry = '
SELECT
studiensemester_kurzbz,
start,
ende
FROM
public.tbl_studiensemester
WHERE
SUBSTRING(studiensemester_kurzbz FROM 1 FOR 2) = SUBSTRING(? FROM 1 FOR 2)
AND start <= (
SELECT start
FROM public.tbl_studiensemester
WHERE studiensemester_kurzbz = ?
)
ORDER BY
start DESC
LIMIT ?
';
return $this->execQuery($qry,
[
$studiensemester_kurzbz,
$studiensemester_kurzbz,
$limit
]
);
}
/** /**
* getNearest * getNearest
*/ */
@@ -260,7 +260,6 @@ class Benutzerfunktion_model extends DB_Model
return $this->execQuery($query, $parameters_array); return $this->execQuery($query, $parameters_array);
} }
/** /**
* Get active Kompetenzfeldleitung bei UID. * Get active Kompetenzfeldleitung bei UID.
* *
@@ -296,7 +295,6 @@ class Benutzerfunktion_model extends DB_Model
return $this->execQuery($query, $parameters_array); return $this->execQuery($query, $parameters_array);
} }
public function insertBenutzerfunktion($Json) public function insertBenutzerfunktion($Json)
{ {
unset($Json['benutzerfunktion_id']); unset($Json['benutzerfunktion_id']);
@@ -18,10 +18,10 @@ class Reservierung_model extends DB_Model
* *
* @return stdClass * @return stdClass
*/ */
public function getReservierungen($start_date, $end_date, $ort_kurzbz = null) public function getReservierungen($start_date, $end_date, $ort_kurzbz = null, $uid = null)
{ {
$lvplan_reservierungen_query="SELECT r.* , stund.beginn, stund.ende, $lvplan_reservierungen_query = "SELECT r.* , stund.beginn, stund.ende,
CASE CASE
WHEN r.gruppe_kurzbz IS NOT NULL THEN r.gruppe_kurzbz WHEN r.gruppe_kurzbz IS NOT NULL THEN r.gruppe_kurzbz
ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/')) ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/'))
@@ -35,7 +35,7 @@ class Reservierung_model extends DB_Model
LEFT JOIN public.tbl_studiensemester ss2 ON slv.studiensemester_kurzbz = ss2.studiensemester_kurzbz AND ss2.start <=r.datum AND ss2.ende >= r.datum LEFT JOIN public.tbl_studiensemester ss2 ON slv.studiensemester_kurzbz = ss2.studiensemester_kurzbz AND ss2.start <=r.datum AND ss2.ende >= r.datum
WHERE datum >= ? AND datum <= ? AND (ss1.studiensemester_kurzbz IS NOT NULL WHERE datum >= ? AND datum <= ? AND (ss1.studiensemester_kurzbz IS NOT NULL
OR ss2.studiensemester_kurzbz IS NOT NULL)"; OR ss2.studiensemester_kurzbz IS NOT NULL)";
$raum_reservierungen_query = "SELECT res.*, beginn, ende, $raum_reservierungen_query = "SELECT res.*, beginn, ende,
CASE CASE
WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz
@@ -46,9 +46,9 @@ class Reservierung_model extends DB_Model
JOIN lehre.tbl_stunde ON lehre.tbl_stunde.stunde = res.stunde JOIN lehre.tbl_stunde ON lehre.tbl_stunde.stunde = res.stunde
WHERE res.ort_kurzbz = ? AND datum >= ? AND datum <= ?"; WHERE res.ort_kurzbz = ? AND datum >= ? AND datum <= ?";
$subquery = is_null($ort_kurzbz)? $lvplan_reservierungen_query:$raum_reservierungen_query; $subquery = is_null($ort_kurzbz) ? $lvplan_reservierungen_query : $raum_reservierungen_query;
$query_result= $this->execReadOnlyQuery(" $query_result = $this->execReadOnlyQuery("
SELECT SELECT
'reservierung' as type, beginn, ende, datum, 'reservierung' as type, beginn, ende, datum,
COALESCE(titel, beschreibung) as topic, COALESCE(titel, beschreibung) as topic,
@@ -59,15 +59,15 @@ class Reservierung_model extends DB_Model
FROM FROM
( (
". $subquery ." " . $subquery . "
) AS subquery ) AS subquery
GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung
ORDER BY datum, beginn ORDER BY datum, beginn
", is_null($ort_kurzbz) ?[getAuthUID(), getAuthUID(),$start_date,$end_date]: [$ort_kurzbz, $start_date, $end_date]); ", is_null($ort_kurzbz) ? [$uid ?? getAuthUID(), $uid ?? getAuthUID(), $start_date, $end_date] : [$ort_kurzbz, $start_date, $end_date]);
return $query_result; return $query_result;
} }
@@ -76,7 +76,7 @@ class Reservierung_model extends DB_Model
* *
* @return stdClass * @return stdClass
*/ */
public function getReservierungenMitarbeiter($start_date, $end_date) public function getReservierungenMitarbeiter($start_date, $end_date, $uid = null)
{ {
$raum_reservierungen_query = "SELECT res.*, beginn, ende, $raum_reservierungen_query = "SELECT res.*, beginn, ende,
@@ -91,8 +91,8 @@ class Reservierung_model extends DB_Model
$subquery = $raum_reservierungen_query; $subquery = $raum_reservierungen_query;
$query_result= $this->execReadOnlyQuery(" $query_result = $this->execReadOnlyQuery("
SELECT SELECT
'reservierung' as type, beginn, ende, datum, 'reservierung' as type, beginn, ende, datum,
COALESCE(titel, beschreibung) as topic, COALESCE(titel, beschreibung) as topic,
@@ -103,13 +103,13 @@ class Reservierung_model extends DB_Model
FROM FROM
( (
". $subquery ." " . $subquery . "
) AS subquery ) AS subquery
GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung
ORDER BY datum, beginn ORDER BY datum, beginn
", [getAuthUID(), $start_date, $end_date]); ", [$uid ?? getAuthUID(), $start_date, $end_date]);
return $query_result; return $query_result;
@@ -129,9 +129,9 @@ class Reservierung_model extends DB_Model
$this->addJoin('public.tbl_studiensemester ss2', 'slv.studiensemester_kurzbz=ss2.studiensemester_kurzbz AND ss2.start<=r.datum AND ss2.ende>=r.datum', 'LEFT'); $this->addJoin('public.tbl_studiensemester ss2', 'slv.studiensemester_kurzbz=ss2.studiensemester_kurzbz AND ss2.start<=r.datum AND ss2.ende>=r.datum', 'LEFT');
$this->db->or_where('ss1.studiensemester_kurzbz IS NOT NULL', null, false); $this->db->or_where('ss1.studiensemester_kurzbz IS NOT NULL', null, false);
$this->db->or_where('ss2.studiensemester_kurzbz IS NOT NULL', null, false); $this->db->or_where('ss2.studiensemester_kurzbz IS NOT NULL', null, false);
$query = $this->db->get_compiled_select('campus.vw_reservierung r'); $query = $this->db->get_compiled_select('campus.vw_reservierung r');
return $this->execQuery($query, [$uid, $uid]); return $this->execQuery($query, [$uid, $uid]);
} }
@@ -388,6 +388,84 @@ class Stundenplan_model extends DB_Model
ORDER BY datum, beginn", [$start_date, $end_date, $ma_uid]); ORDER BY datum, beginn", [$start_date, $end_date, $ma_uid]);
} }
/**
* queries Stundenplan and filters by studiengang, semester, verband gruppe
*
* @return void
*/
public function getStundenplanStudiengang($start_date, $end_date, $stg_kz, $sem, $verband, $gruppe) {
$qry_params = [$start_date, $end_date, $stg_kz];
$qry = "
SELECT
'lehreinheit' as type, beginn, ende, datum,
CONCAT(lehrfach,'-',lehrform) as topic,
array_agg(DISTINCT lektor) as lektor,
array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe,
string_agg(DISTINCT ort_kurzbz, '/') as ort_kurzbz,
array_agg(DISTINCT lehreinheit_id) as lehreinheit_id,
titel, lehrfach, lehrform, lehrfach_bez, organisationseinheit, farbe, lehrveranstaltung_id
FROM
(
SELECT unr,datum,beginn, ende,
CASE
WHEN sp.mitarbeiter_kurzbz IS NOT NULL THEN sp.mitarbeiter_kurzbz
ELSE sp.lektor
END as lektor,
CASE
WHEN gruppe_kurzbz IS NOT NULL THEN gruppe_kurzbz
ELSE CONCAT(UPPER(sp.stg_typ),UPPER(sp.stg_kurzbz),'-',COALESCE(CAST(sp.semester AS varchar),'/'),COALESCE(CAST(sp.verband AS varchar),'/'))
END as gruppen_kuerzel,
(SELECT bezeichnung
FROM public.tbl_organisationseinheit
WHERE oe_kurzbz IN(
SELECT oe_kurzbz
FROM lehre.tbl_lehrveranstaltung
WHERE lehrveranstaltung_id = sp.lehrveranstaltung_id
)) as organisationseinheit,
sp.ort_kurzbz, sp.studiengang_kz, sp.titel,sp.lehreinheit_id,sp.lehrfach_id,sp.anmerkung,fix,lehrveranstaltung_id,stg_kurzbzlang,stg_bezeichnung,stg_typ,fachbereich_kurzbz,lehrfach,lehrfach_bez,farbe,lehrform,anmerkung_lehreinheit,gruppe, verband, semester,stg_kurzbz
FROM (
SELECT sp.*
FROM lehre.vw_stundenplan sp
WHERE
sp.datum >= ?
AND sp.datum <= ?
) sp
JOIN lehre.tbl_stunde ON lehre.tbl_stunde.stunde = sp.stunde
WHERE studiengang_kz = ? ";
if($sem != NULL)
{
$qry_params[] = $sem;
$qry .= " AND (semester = ? OR semester IS NULL)";
}
if($verband != NULL)
{
$qry_params[] = $verband;
$qry .= " AND (verband = ? OR verband IS NULL OR verband = '0' OR verband = '')";
}
if($gruppe != NULL)
{
$qry_params[] = $gruppe;
$qry .= " AND (gruppe = ? OR gruppe IS NULL OR gruppe = '0' OR gruppe = '') ";
}
$qry.= " AND (
gruppe_kurzbz is null OR EXISTS(
SELECT 1
FROM
public.tbl_gruppe WHERE gruppe_kurzbz = sp.gruppe_kurzbz AND direktinskription = false
)
)";
$qry.= " ) as subquery
GROUP BY unr, datum, beginn, ende, titel, lehrform, lehrfach, lehrfach_bez, organisationseinheit, farbe, lehrveranstaltung_id
ORDER BY datum, beginn; ";
return $this->execReadOnlyQuery($qry, $qry_params);
}
/** /**
* NO STANDALONE FUNCTION - Generates a SQL query string to fetch 'stundenplan' events for a specific student within the current semester. * NO STANDALONE FUNCTION - Generates a SQL query string to fetch 'stundenplan' events for a specific student within the current semester.
+1 -1
View File
@@ -38,7 +38,7 @@ $includesArray = array(
$this->load->view('templates/FHC-Header', $includesArray); $this->load->view('templates/FHC-Header', $includesArray);
?> ?>
<div id="abgabetoolroot" class="h-100" style="max-width: 95%;" route=<?php echo json_encode($route) ?> <div id="abgabetoolroot" class="h-100" style="max-width: 99%" route=<?php echo json_encode($route) ?>
uid=<?php echo $uid ?> uid=<?php echo $uid ?>
student_uid_prop="<?php echo $student_uid_prop ?? '' ?>" student_uid_prop="<?php echo $student_uid_prop ?? '' ?>"
stg_kz_prop="<?php echo $stg_kz_prop ?? '' ?>" stg_kz_prop="<?php echo $stg_kz_prop ?? '' ?>"
@@ -23,12 +23,14 @@ $includesArray = array(
'public/css/components/FormUnderline.css', 'public/css/components/FormUnderline.css',
'public/css/components/abgabetool/abgabe.css', 'public/css/components/abgabetool/abgabe.css',
'public/css/Cis4/Cms.css', 'public/css/Cis4/Cms.css',
'public/css/Cis4/Studium.css', 'public/css/Cis4/Studium.css'
), ),
'customJSs' => array( 'customJSs' => array(
'vendor/npm-asset/primevue/accordion/accordion.min.js', 'vendor/npm-asset/primevue/accordion/accordion.min.js',
'vendor/npm-asset/primevue/accordiontab/accordiontab.min.js', 'vendor/npm-asset/primevue/accordiontab/accordiontab.min.js',
'vendor/npm-asset/primevue/checkbox/checkbox.min.js', 'vendor/npm-asset/primevue/checkbox/checkbox.min.js',
'vendor/npm-asset/primevue/chips/chips.min.js',
'vendor/npm-asset/primevue/multiselect/multiselect.min.js',
'vendor/npm-asset/primevue/inputnumber/inputnumber.min.js', 'vendor/npm-asset/primevue/inputnumber/inputnumber.min.js',
'vendor/npm-asset/primevue/speeddial/speeddial.min.js', 'vendor/npm-asset/primevue/speeddial/speeddial.min.js',
'vendor/npm-asset/primevue/textarea/textarea.min.js', 'vendor/npm-asset/primevue/textarea/textarea.min.js',
@@ -39,7 +41,7 @@ $includesArray = array(
'vendor/moment/luxonjs/luxon.min.js' 'vendor/moment/luxonjs/luxon.min.js'
), ),
'customJSModules' => array( 'customJSModules' => array(
'public/js/apps/Dashboard/Fhc.js', 'public/js/apps/Cis/Cis.js',
), ),
); );
@@ -47,8 +49,6 @@ $includesArray = array(
$this->load->view('templates/CISVUE-Header', $includesArray); $this->load->view('templates/CISVUE-Header', $includesArray);
?> ?>
<div id="fhccontent" class="h-100" route=<?php echo $route ?>> <div id="fhccontent" class="h-100" route=<?php echo $route ?>>
<router-view <router-view></router-view>
:view-data='<?php echo json_encode($viewData) ?>'
></router-view>
</div> </div>
<?php $this->load->view('templates/CISVUE-Footer', $includesArray); ?> <?php $this->load->view('templates/CISVUE-Footer', $includesArray); ?>
@@ -44,6 +44,7 @@ $this->load->view(
<studierendenantrag-leitung <studierendenantrag-leitung
:stg-a="<?= htmlspecialchars(json_encode(array_values($stgA))); ?>" :stg-a="<?= htmlspecialchars(json_encode(array_values($stgA))); ?>"
:stg-l="<?= htmlspecialchars(json_encode(array_values($stgL))); ?>" :stg-l="<?= htmlspecialchars(json_encode(array_values($stgL))); ?>"
:abmeldung_enabled="<?= htmlspecialchars(json_encode(($abmeldung_enabled))); ?>"
> >
</studierendenantrag-leitung> </studierendenantrag-leitung>
@@ -6,7 +6,7 @@ $includesArray = array(
'fontawesome6' => true, 'fontawesome6' => true,
'axios027' => true, 'axios027' => true,
'customJSModules' => array_merge([ 'customJSModules' => array_merge([
'public/js/apps/Cis.js' 'public/js/apps/Cis/Menu.js'
], $customJSModules ?? []), ], $customJSModules ?? []),
'customCSSs' => array_merge([ 'customCSSs' => array_merge([
'public/css/Cis4/Cis.css' 'public/css/Cis4/Cis.css'
@@ -8,7 +8,7 @@ $includesArray = array(
'axios027' => true, 'axios027' => true,
'primevue3' => true, 'primevue3' => true,
'customJSModules' => array_merge([ 'customJSModules' => array_merge([
'public/js/apps/Cis.js' 'public/js/apps/Cis/Menu.js'
], $customJSModules ?? []), ], $customJSModules ?? []),
'customCSSs' => array_merge([ 'customCSSs' => array_merge([
'public/css/Cis4/Cis.css', 'public/css/Cis4/Cis.css',
+17 -3
View File
@@ -36,6 +36,7 @@ require_once('../../../include/notenschluessel.class.php');
require_once('../../../include/Excel/excel.php'); require_once('../../../include/Excel/excel.php');
require_once('../../../include/phrasen.class.php'); require_once('../../../include/phrasen.class.php');
require_once('../../../include/pruefung.class.php'); require_once('../../../include/pruefung.class.php');
require_once('../../../include/benutzerberechtigung.class.php');
$uid = get_uid(); $uid = get_uid();
@@ -44,7 +45,7 @@ $uid = get_uid();
$sprache = getSprache(); $sprache = getSprache();
$p = new phrasen($sprache); $p = new phrasen($sprache);
if(!check_lektor($uid)) if (!check_lektor($uid))
die('Sie haben keine Berechtigung fuer diese Seite'); die('Sie haben keine Berechtigung fuer diese Seite');
if (!$db = new basis_db()) if (!$db = new basis_db())
@@ -90,6 +91,21 @@ if(isset($_GET['lehreinheit_id']))
else else
$lehreinheit_id = ''; $lehreinheit_id = '';
// Permissions
$berechtigung = new benutzerberechtigung();
$berechtigung->getBerechtigungen($uid);
// LV load
$lvobj = new lehrveranstaltung($lvid);
// Check permissions
if (!$berechtigung->isBerechtigt('admin')
&& !$berechtigung->isBerechtigt('assistenz')
&& !$berechtigung->isBerechtigt('lehre', $lvobj->oe_kurzbz, 's')
&& !check_lektor_lehrveranstaltung($uid, $lvid, $stsem)
)
die('Sie haben keine Berechtigung fuer diese Seite');
/* /*
* Create Excel File * Create Excel File
*/ */
@@ -143,8 +159,6 @@ else
// let's merge // let's merge
$format_title->setAlign('merge'); $format_title->setAlign('merge');
$lvobj = new lehrveranstaltung($lvid);
$worksheet->write(0,0,$p->t('anwesenheitsliste/notenliste')." ".($sprache=='English'?$lvobj->bezeichnung_english:$lvobj->bezeichnung),$format_bold); $worksheet->write(0,0,$p->t('anwesenheitsliste/notenliste')." ".($sprache=='English'?$lvobj->bezeichnung_english:$lvobj->bezeichnung),$format_bold);
$stg_obj = new studiengang($stg); $stg_obj = new studiengang($stg);
+18
View File
@@ -70,6 +70,18 @@
} }
} }
}, },
{
"type": "package",
"package": {
"name": "drag-drop-touch-js/dragdroptouch",
"version": "2.0.3",
"source": {
"url": "https://github.com/drag-drop-touch-js/dragdroptouch.git",
"type": "git",
"reference": "master"
}
}
},
{ {
"type": "package", "type": "package",
"package": { "package": {
@@ -452,6 +464,8 @@
"easyrdf/easyrdf": "0.9.*", "easyrdf/easyrdf": "0.9.*",
"drag-drop-touch-js/dragdroptouch": "*",
"fgelinas/timepicker": "0.3.3", "fgelinas/timepicker": "0.3.3",
"fortawesome/font-awesome4": "4.7.*", "fortawesome/font-awesome4": "4.7.*",
"fortawesome/font-awesome6": "6.1.*", "fortawesome/font-awesome6": "6.1.*",
@@ -519,5 +533,9 @@
"phpmetrics/phpmetrics": "2.*", "phpmetrics/phpmetrics": "2.*",
"sebastian/phpcpd": "3.*", "sebastian/phpcpd": "3.*",
"phpunit/phpunit": "^6" "phpunit/phpunit": "^6"
},
"scripts": {
"post-install-cmd": "@symlink_vendor_to_public",
"symlink_vendor_to_public": "ln -sfn ../vendor ./public/"
} }
} }
Generated
+11 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "f4f0af4586f46f97d8b6092c1ac0fb3a", "content-hash": "869cbc35bd1ba90ab90934fcb41b0f51",
"packages": [ "packages": [
{ {
"name": "afarkas/html5shiv", "name": "afarkas/html5shiv",
@@ -804,6 +804,16 @@
"abandoned": true, "abandoned": true,
"time": "2018-03-09T06:07:41+00:00" "time": "2018-03-09T06:07:41+00:00"
}, },
{
"name": "drag-drop-touch-js/dragdroptouch",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/drag-drop-touch-js/dragdroptouch.git",
"reference": "master"
},
"type": "library"
},
{ {
"name": "easyrdf/easyrdf", "name": "easyrdf/easyrdf",
"version": "0.9.1", "version": "0.9.1",
+4
View File
@@ -373,4 +373,8 @@ define('TESTTOOL_EXTERNE_UEBERWACHUNG_ALLOWED', false);
//enable tags in StudVW //enable tags in StudVW
define('STV_TAGS_ENABLED', false); define('STV_TAGS_ENABLED', false);
//student accounts grace period
define('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_PERIOD', '0 days');
define('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_ROLES', serialize(array('NO_DEFINED_ROLE')));
?> ?>
+36 -1
View File
@@ -340,6 +340,40 @@ class benutzerberechtigung extends basis_db
return true; return true;
} }
protected function hasPreStudentStatusInGracePeriod($uid)
{
$period = defined('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_PERIOD')
? STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_PERIOD
: '0 days';
$mapfunc = function($val) {
return $this->db_add_param($val);
};
$roles = defined('STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_ROLES')
? implode(', ', array_map($mapfunc, unserialize(STUDENTS_KEEP_PERMISSIONS_AFTER_USER_INACTIVE_ROLES)))
: "'NO_DEFINED_ROLE'";
$sql = <<<EOSQL
SELECT
1
FROM
campus.vw_student vs
WHERE
vs.uid = {$this->db_add_param($uid)}
AND
vs.aktiv = false
AND
public.get_rolle_prestudent(vs.prestudent_id, null) IN ({$roles})
AND
(vs.updateaktivam + INTERVAL {$this->db_add_param($period)})::date >= CURRENT_DATE
EOSQL;
$result = $this->db_query($sql);
if($result && $this->db_num_rows($result) > 0)
{
return true;
}
return false;
}
/** /**
* Laedt die Berechtigungen eines Users * Laedt die Berechtigungen eines Users
* @param $uid * @param $uid
@@ -355,7 +389,8 @@ class benutzerberechtigung extends basis_db
if($row = $this->db_fetch_object($result)) if($row = $this->db_fetch_object($result))
{ {
// Wenn die Person nicht aktiv ist dann hat diese auch keine Rechte // Wenn die Person nicht aktiv ist dann hat diese auch keine Rechte
if($this->db_parse_bool($row->aktiv) == false) if($this->db_parse_bool($row->aktiv) == false
&& $this->hasPreStudentStatusInGracePeriod($uid) === false)
return false; return false;
} }
else else
+28 -35
View File
@@ -147,6 +147,8 @@ html {
--fhc-cis-menu-lvl-5-color-hover: var(--fhc-text); --fhc-cis-menu-lvl-5-color-hover: var(--fhc-text);
--fhc-cis-grade-positive: var(--fhc-success); --fhc-cis-grade-positive: var(--fhc-success);
--fhc-cis-grade-negative: var(--fhc-danger); --fhc-cis-grade-negative: var(--fhc-danger);
--fhc-offcanvas-zindex: 1045;
} }
#themeSwitch i{ #themeSwitch i{
@@ -375,7 +377,7 @@ html {
/* searchbar */ /* searchbar */
#nav-search { #nav-search {
background-color: var(--fhc-primary); background-color: var(--fhc-primary);
z-index: 1; z-index: calc(var(--fhc-offcanvas-zindex) + 1) !important;
} }
#nav-search.me-3 { #nav-search.me-3 {
margin: 0 !important; margin: 0 !important;
@@ -386,12 +388,6 @@ html {
#nav-search > .input-group > * { #nav-search > .input-group > * {
border-radius: 0 !important; border-radius: 0 !important;
} }
#nav-search .searchbar_results {
top: 100% !important;
left: 0;
right: 0 !important;
width: 100% !important;
}
/* frame */ /* frame */
.in-frame { .in-frame {
@@ -413,10 +409,18 @@ html {
color: var(--fhc-link) !important; color: var(--fhc-link) !important;
} }
#nav-main {
z-index: var(--fhc-offcanvas-zindex);
}
#nav-main-sticky { #nav-main-sticky {
max-height: calc(100vh - var(--fhc-cis-header-height)); max-height: calc(100vh - var(--fhc-cis-header-height));
} }
#nav-user-menu {
z-index: calc(var(--fhc-offcanvas-zindex) + 1);
}
#nav-user-menu img { #nav-user-menu img {
object-fit: cover; object-fit: cover;
height: calc( 3 * var(--fhc-cis-header-py)); height: calc( 3 * var(--fhc-cis-header-py));
@@ -457,7 +461,14 @@ html {
/* overflow: visible !important; */ /* overflow: visible !important; */
} }
#cis-header { #cis-header {
z-index: 10; z-index: 10;
}
#cis-header-bar {
position: fixed;
top: 0;
height: var(--fhc-cis-header-height);
width: 100%;
background-color: var(--fhc-primary);
} }
#cis-header nav { #cis-header nav {
position: initial; position: initial;
@@ -473,12 +484,7 @@ html {
display: none; display: none;
} }
#nav-logo { #nav-logo {
position: fixed;
top: 0;
left: 0;
height: var(--fhc-cis-header-height);
width: var(--fhc-cis-menu-width); width: var(--fhc-cis-menu-width);
background-color: var(--fhc-primary);
padding: var(--fhc-cis-header-py) var(--fhc-cis-header-px); padding: var(--fhc-cis-header-py) var(--fhc-cis-header-px);
z-index: 2; z-index: 2;
} }
@@ -493,37 +499,27 @@ html {
top: var(--fhc-cis-header-height); top: var(--fhc-cis-header-height);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} height: 100%;
#nav-main-sticky > :not(#nav-main-toggle) {
overflow: auto;
} }
#nav-main-toggle { #nav-main-toggle {
width: 0;
z-index: 1; z-index: 1;
} }
#nav-main-toggle .btn, #nav-main-toggle:hover {
#nav-main-toggle .fa-arrow-circle-left { background-color: var(--fhc-secondary-highlight);
}
#nav-main-toggle .div,
#nav-main-toggle .fa-chevron-left {
transition: all 0.5s ease-in-out; transition: all 0.5s ease-in-out;
} }
#nav-main-toggle .collapsed.btn { #nav-main-toggle .collapsed.btn {
background-color: transparent !important; background-color: transparent !important;
} }
#nav-main-toggle .collapsed .fa-arrow-circle-left { #nav-main-toggle .collapsed .fa-chevron-left {
transform: scaleX(-1); transform: scaleX(-1);
color: var(--fhc-black-40); color: var(--fhc-black-40);
} }
#nav-search { #nav-search {
position: fixed; position: relative;
top: 0;
left: var(--fhc-cis-menu-width);
height: var(--fhc-cis-header-height);
right: calc(var(--fhc-cis-header-height) + 2 * var(--fhc-cis-header-px) - 2 * var(--fhc-cis-header-py));
width: auto !important;
}
#nav-user {
position: fixed;
top: 0;
right: 0;
} }
#nav-user-btn { #nav-user-btn {
border-width: 0; border-width: 0;
@@ -558,7 +554,7 @@ html {
#nav-main-menu { #nav-main-menu {
height: 100%; height: 100%;
background-color: var(--fhc-cis-menu-bg); background-color: var(--fhc-primary);
} }
#nav-main-menu > div { #nav-main-menu > div {
width: var(--fhc-cis-menu-width); width: var(--fhc-cis-menu-width);
@@ -600,9 +596,6 @@ html {
} }
#nav-user{ #nav-user{
position: relative; position: relative;
}
#nav-user-btn {
} }
#nav-user-btn img { #nav-user-btn img {
object-fit: cover; object-fit: cover;
+4
View File
@@ -277,3 +277,7 @@ html.fs_huge {
} }
*/ */
/* slim ende */ /* slim ende */
.fhc-xxl-modal {
min-width: 80vw;
}
+101 -1
View File
@@ -305,4 +305,104 @@
/* If you use hover rows, you need to ensure the sticky cell matches the hover color */ /* If you use hover rows, you need to ensure the sticky cell matches the hover color */
#abgabetable .tabulator-row:hover .tabulator-cell.sticky-col { #abgabetable .tabulator-row:hover .tabulator-cell.sticky-col {
background-color: #ccc; /* Match your existing hover color */ background-color: #ccc; /* Match your existing hover color */
} }
.tabulator-cell {
container-type: inline-size;
}
.tabulator-col-title {
container-type: inline-size;
}
@container (max-width: 100px) {
.full-text {
display: none !important;
}
.short-text {
display: inline-block !important;
width: 100%;
}
}
/*conditional tooltips fix*/
.p-tooltip.custom-tooltip {
z-index: 8001 !important;
}
/* Shrinks font and table rows for desktop users who have zoomed in their browser (150%+).
Does not affect mobile/touchscreen devices, which use touch input instead of a mouse. */
@media (pointer: fine) and (min-resolution: 1.5dppx) {
html.abgabetool {
font-size: 0.5rem;
}
.abgabetool .tabulator-cell,
.abgabetool .tabulator-row {
height: 20px;
max-height: 20px;
}
}
/*confetti celebration on endupload - impossible to miss*/
#confetti-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9999;
overflow: hidden;
}
.confetti-piece {
position: absolute;
opacity: 0;
will-change: top, transform, opacity;
}
/* Background Rain */
@keyframes fallAndSpin {
0% {
top: var(--start-y);
transform: translate3d(0, 0, 0) rotateX(0deg) rotateY(0deg);
opacity: 1;
}
100% {
top: 105vh;
transform: translate3d(var(--drift), 0, 0) rotateX(720deg) rotateY(360deg);
opacity: 0.3;
}
}
/* Corner Cannons*/
@keyframes cannonBlast {
0% {
transform: translate3d(0, 0, 0) scale(0.3) rotate(0deg);
opacity: 1;
animation-timing-function: cubic-bezier(0.1, 0.8, 0.2, 1);
}
30% {
transform: translate3d(var(--blast-x), var(--blast-y), 0) scale(1.2) rotate(270deg);
opacity: 1;
animation-timing-function: linear;
}
100% {
transform: translate3d(calc(var(--blast-x) * 1.4), 15vh, 0) scale(0.4) rotate(630deg);
opacity: 0;
}
}
.p-timeline-event-opposite {
flex: 0 0 90px;
max-width: 90px;
}
.p-timeline-event-content {
flex: 1 1 auto;
min-width: 0;
}
+60 -52
View File
@@ -2,7 +2,7 @@
@import './dashboard/news.css'; @import './dashboard/news.css';
@import './dashboard/LvPlan.css'; @import './dashboard/LvPlan.css';
:root{ :root {
--fhc-dashboard-danger: var(--fhc-danger, #842029); --fhc-dashboard-danger: var(--fhc-danger, #842029);
--fhc-dashboard-grid-size: 4; --fhc-dashboard-grid-size: 4;
--fhc-dashboard-link: var(--fhc-link, #0a57ca); --fhc-dashboard-link: var(--fhc-link, #0a57ca);
@@ -17,22 +17,16 @@
--fhc-dashboard-section-info-color-hover: var(--fhc-primary-highlight, #005585); --fhc-dashboard-section-info-color-hover: var(--fhc-primary-highlight, #005585);
} }
@media(max-width: 577px) { .core-dashboard a {
:root {
--fhc-dashboard-grid-size: 1;
}
}
.core-dashboard a{
color: var(--fhc-dashboard-link); color: var(--fhc-dashboard-link);
} }
@media (max-width: 576px){ @media (max-width: 576px) {
.widget-icon { .widget-icon {
max-height: 250px; max-height: 250px;
object-fit: cover; object-fit: cover;
} }
.widget-icon-container{ .widget-icon-container {
max-width: 250px; max-width: 250px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@@ -46,27 +40,36 @@
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
cursor:pointer; cursor: pointer;
} }
.dashboard-section > .newGridRow{ .dashboard-section.edit-active {
position:absolute; /**
width:20px; * replaces margin for extra row
height:20px; * 10% equals 0.1 of 100%
padding:0; * 1rem equals the padding of pb-3 that is overwritten here
bottom:0; */
left:50%; padding-bottom: calc(10% / var(--fhc-dashboard-grid-size) + 1rem) !important;
transform:translate(-50%, 50%); }
.dashboard-section > .newGridRow {
position: absolute;
width: 20px;
height: 20px;
padding: 0;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
background-color: var(--fhc-dashboard-gridrow-background); background-color: var(--fhc-dashboard-gridrow-background);
} }
.newGridRow:hover { .newGridRow:hover {
color:white; color: white;
background-color: var(--fhc-dashboard-gridrow-background-highlight); background-color: var(--fhc-dashboard-gridrow-background-highlight);
} }
.empty-tile-hover:hover { .empty-tile-hover:hover {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-500 -500 1448 1512"><path fill="rgb(211, 211, 211)" d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM200 344V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H248v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>'); background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-500 -500 1448 1512"><path fill="rgb(211, 211, 211)" d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM200 344V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H248v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>');
} }
.alert-danger .form-check-input:checked { .alert-danger .form-check-input:checked {
@@ -74,16 +77,6 @@
background-color: var(--fhc-dashboard-danger); background-color: var(--fhc-dashboard-danger);
} }
:root {
--fhc-dashboard-grid-size: 4;
}
@media(max-width: 1400px) {
:root {
--fhc-dashboard-grid-size: 4;
}
}
@media(max-width: 1200px) { @media(max-width: 1200px) {
:root { :root {
--fhc-dashboard-grid-size: 3; --fhc-dashboard-grid-size: 3;
@@ -105,6 +98,7 @@
@media(max-width: 577px) { @media(max-width: 577px) {
:root { :root {
--fhc-dashboard-grid-size: 1; --fhc-dashboard-grid-size: 1;
--fhc-dg-item-py: .75rem;
} }
} }
@@ -132,50 +126,64 @@
cursor: move !important; cursor: move !important;
} }
.draggedItem { .drop-grid-item-resize > .dashboard-item,
.drop-grid-item-move > .dashboard-item {
height: 100%; height: 100%;
width: 100%; width: 100%;
background-color: var(--fhc-dashboard-draggeditem-background); background-color: var(--fhc-dashboard-draggeditem-background);
position: relative; position: relative;
} }
.dashboard-item-overlay{ .drop-grid-item-resize > .dashboard-item > *,
.drop-grid-item-move > .dashboard-item > * {
display: none;
}
.drop-grid-item-sizechanged > .dashboard-item,
.drop-grid-item-move > .dashboard-item {
background-color: var(--fhc-dashboard-item-overlay-background); background-color: var(--fhc-dashboard-item-overlay-background);
} }
.dashboard-item-overlay::before{ .drop-grid-item-sizechanged > .dashboard-item::before,
position:absolute; .drop-grid-item-move > .dashboard-item::before {
content:""; position: absolute;
top:0.25rem; content: "";
left:0.25rem; top: .25rem;
right:0.25rem; left: .25rem;
bottom:0.25rem; right: .25rem;
border:4px dashed var(--fhc-dashboard-item-overly-border-color); bottom: .25rem;
opacity: 0.5; border: 4px dashed var(--fhc-dashboard-item-overly-border-color);
opacity: .5;
} }
#deleteBookmark i{ .drop-grid-item-oversized > .dashboard-item {
/* Bootstrap: border-danger */
--bs-border-opacity: 1;
border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important;
}
#deleteBookmark i {
color: var(--fhc-dashboard-danger); color: var(--fhc-dashboard-danger);
} }
.pin:hover{ .pin:hover {
cursor: pointer; cursor: pointer;
} }
.pin[pinned]:hover{ .pin[pinned]:hover {
color: var(--fhc-dashboard-pin-pinned-hover-color); color: var(--fhc-dashboard-pin-pinned-hover-color);
} }
.section-info{ .section-info {
color: var(--fhc-dashboard-section-info-color); color: var(--fhc-dashboard-section-info-color);
cursor:pointer; cursor: pointer;
} }
.section-info:hover { .section-info:hover {
color: var(--fhc-dashboard-section-info-color-hover); color: var(--fhc-dashboard-section-info-color-hover);
} }
.denied-dragging-animation { .drop-grid-item-blocker [pinned='true'] {
animation: wiggle 0.5s linear; animation: wiggle 0.5s linear;
color: var(--fhc-dashboard-denied-dragging-animation-color) !important; color: var(--fhc-dashboard-denied-dragging-animation-color) !important;
} }
@@ -204,13 +212,13 @@
} }
.hiddenWidget{ .hidden-widget {
background: var(--fhc-disabled-background); background: var(--fhc-disabled-background);
opacity: 40%; opacity: 40%;
} }
.hiddenWidget .card, .hidden-widget .card,
.hiddenWidget .card-body, .hidden-widget .card-body,
.hiddenWidget .card-body *{ .hidden-widget .card-body * {
background: inherit !important; background: inherit !important;
} }
+38
View File
@@ -77,6 +77,13 @@ export default {
} }
}; };
}, },
patchProjektarbeitAbgabeMultiple(payload) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/patchProjektarbeitAbgabeMultiple',
params: payload
};
},
deleteProjektarbeitAbgabe(paabgabe_id) { deleteProjektarbeitAbgabe(paabgabe_id) {
return { return {
method: 'post', method: 'post',
@@ -84,6 +91,13 @@ export default {
params: { paabgabe_id } params: { paabgabe_id }
}; };
}, },
deleteProjektarbeitAbgabeMultiple(paabgabe_ids) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/deleteProjektarbeitAbgabeMultiple',
params: { paabgabe_ids }
};
},
postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, upload_allowed, projektarbeit_ids, fixtermin) { postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, upload_allowed, projektarbeit_ids, fixtermin) {
return { return {
method: 'post', method: 'post',
@@ -139,6 +153,30 @@ export default {
url: '/api/frontend/v1/Abgabe/getSignaturStatusForProjektarbeitAbgaben', url: '/api/frontend/v1/Abgabe/getSignaturStatusForProjektarbeitAbgaben',
params: {paabgabe_ids, student_uid}, params: {paabgabe_ids, student_uid},
};
},
postStudentProjektarbeitTitel(projektarbeit_id, titel) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/postStudentProjektarbeitTitel',
params: {projektarbeit_id, titel},
};
},
sendZweitbetreuerTokenMail(projektarbeit_id, betreuer_person_id, student_uid) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/sendZweitbetreuerTokenMail',
params: {projektarbeit_id, bperson_id: betreuer_person_id, student_uid},
};
},
fetchProjektarbeitenHistory(student_uid) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/fetchProjektarbeitenHistory',
params: {student_uid},
}; };
} }
}; };
+1 -1
View File
@@ -19,7 +19,7 @@ export default {
getViewData() { getViewData() {
return { return {
method: 'get', method: 'get',
url: '/api/frontend/v1/Cis4FhcApi/getViewData' url: '/api/frontend/v1/Cis4FhcApi/dashboardViewData'
}; };
} }
}; };
+26
View File
@@ -0,0 +1,26 @@
/**
* 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/>.
*/
export default {
getAllStudienSemester(studiensemester, studiengang, semester, studienplan) {
return {
method: 'get',
url: 'api/frontend/v1/Studium/getStudienAllSemester/',
params: {studiensemester, studiengang, semester, studienplan}
};
},
}
+6
View File
@@ -35,5 +35,11 @@ export default {
method: 'get', method: 'get',
url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}` url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}`
}; };
},
getSemesterAverageGrade(semester) {
return {
method: 'get',
url: `/api/frontend/v1/Lehre/semesterAverageGrade/${semester}`
}
} }
}; };
+48 -5
View File
@@ -16,6 +16,12 @@
*/ */
export default { export default {
getMyLvPlanViewData() {
return {
method: 'get',
url: `/api/frontend/v1/LvPlan/myLvPlanViewData`,
};
},
getRoomInfo(ort_kurzbz, start_date, end_date) { getRoomInfo(ort_kurzbz, start_date, end_date) {
return { return {
method: 'post', method: 'post',
@@ -30,11 +36,11 @@ export default {
params: { start_date, end_date, lv_id } params: { start_date, end_date, lv_id }
}; };
}, },
eventsPersonal(start_date, end_date) { eventsPersonal(start_date, end_date, uid = null) {
return { return {
method: 'post', method: 'post',
url: '/api/frontend/v1/lvPlan/eventsPersonal', url: '/api/frontend/v1/lvPlan/eventsPersonal',
params: { start_date, end_date } params: { start_date, end_date, uid }
}; };
}, },
eventsLv(lv_id, start_date, end_date) { eventsLv(lv_id, start_date, end_date) {
@@ -57,11 +63,11 @@ export default {
params: { start_date, end_date } params: { start_date, end_date }
}; };
}, },
getLvPlanReservierungen(start_date, end_date) { getLvPlanReservierungen(start_date, end_date, uid = null) {
return { return {
method: 'post', method: 'post',
url: '/api/frontend/v1/LvPlan/getReservierungen', url: '/api/frontend/v1/LvPlan/getReservierungen',
params: { start_date, end_date } params: { start_date, end_date, uid }
}; };
}, },
getLehreinheitStudiensemester(lehreinheit_id) { getLehreinheitStudiensemester(lehreinheit_id) {
@@ -92,5 +98,42 @@ export default {
method: 'get', method: 'get',
url: '/api/frontend/v1/LvPlan/getLv/' + lehrveranstaltung_id url: '/api/frontend/v1/LvPlan/getLv/' + lehrveranstaltung_id
}; };
} },
eventsStgOrg(start_date, end_date, stg_kz, sem, verband, gruppe) {
return {
method: 'post',
url: '/api/frontend/v1/lvPlan/eventsStgOrg',
params: { start_date, end_date, stg_kz, sem, verband, gruppe }
};
},
getStudiengaenge(){
return {
method: 'get',
url: '/api/frontend/v1/lvPlan/getStudiengaenge'
}
},
getLehrverband(stg_kz, sem){
return {
method: 'get',
url: `/api/frontend/v1/lvPlan/getLehrverband/${stg_kz}/${sem}`
}
},
getGruppe(stg_kz, sem, verband){
return {
method: 'get',
url: `/api/frontend/v1/lvPlan/getLehrverband/${stg_kz}/${sem}/${verband}`
}
},
checkPermissionOtherLvPlan(){
return {
method: 'get',
url: '/api/frontend/v1/lvPlan/permissionOtherLvPlan',
}
},
getCompactibleEventTypes(){
return {
method: 'get',
url: '/api/frontend/v1/lvPlan/compactibleEventTypes',
}
},
}; };
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (C) 2025 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getOtherLvPlanViewData(uid) {
return {
method: 'get',
url: `/api/frontend/v1/OtherLvPlan/otherLvPlanViewData/${uid}`,
};
},
};
+20 -1
View File
@@ -41,5 +41,24 @@ export default {
method: 'get', method: 'get',
url: '/api/frontend/v1/phrasen/getAllLanguages' url: '/api/frontend/v1/phrasen/getAllLanguages'
}; };
} },
getPhrases(phrasesGroupedByCategory, languages = []) {
return {
method: 'post',
url: '/api/frontend/v1/phrasen/getPhrases',
params: {
phrasesGroupedByCategory,
languages,
},
};
},
getTabulatorPhrases(languages = []) {
return {
method: 'get',
url: '/api/frontend/v1/phrasen/getTabulatorPhrases',
params: {
languages: JSON.stringify(languages),
},
};
},
}; };
+1 -1
View File
@@ -17,7 +17,7 @@
export default { export default {
profilViewData(uid) { getProfilViewData(uid = null) {
let url = "/api/frontend/v1/Profil/profilViewData"; let url = "/api/frontend/v1/Profil/profilViewData";
if(uid){ if(uid){
url += `/${uid}`; url += `/${uid}`;
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (C) 2025 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getStgOrgLvPlanViewData(uid) {
return {
method: 'get',
url: `/api/frontend/v1/StgOrgLvPlan/stgOrgLvPlanViewData`,
};
},
};
+11 -4
View File
@@ -17,6 +17,7 @@
export default { export default {
getBookmarks() { getBookmarks() {
return { return {
method: 'get', method: 'get',
url: '/api/frontend/v1/Bookmark/getBookmarks' url: '/api/frontend/v1/Bookmark/getBookmarks'
@@ -28,18 +29,24 @@ export default {
url: `/api/frontend/v1/Bookmark/delete/${bookmark_id}` url: `/api/frontend/v1/Bookmark/delete/${bookmark_id}`
}; };
}, },
update({ bookmark_id, url, title, tag=null }) { update({ bookmark_id, url, title, tag }) {
return { return {
method: 'post', method: 'post',
url: `/api/frontend/v1/Bookmark/update/${bookmark_id}`, url: `/api/frontend/v1/Bookmark/update/${bookmark_id}`,
params: { url, title } params: { url, title, tag }
}; };
}, },
insert({ url, title, tag }) { insert({ url, title, tag, sort }) {
return { return {
method: 'post', method: 'post',
url: `/api/frontend/v1/Bookmark/insert`, url: `/api/frontend/v1/Bookmark/insert`,
params: { url, title, tag } params: { url, title, tag, sort }
}; };
},
changeOrder(bookmark_id1, bookmark_id2) {
return {
method: 'post',
url: `/api/frontend/v1/Bookmark/changeOrder/${bookmark_id1}/${bookmark_id2}`,
};
} }
}; };
+4 -7
View File
@@ -25,9 +25,6 @@ const app = Vue.createApp({
}, },
computed: { computed: {
viewData() {
return { uid: this.uid}
},
student_uid_computed() { student_uid_computed() {
return this.student_uid ?? this.uid return this.student_uid ?? this.uid
}, },
@@ -55,10 +52,10 @@ const app = Vue.createApp({
}, },
template: ` template: `
<template v-if="comp && uid"> <template v-if="comp && uid">
<AbgabetoolStudent v-if="comp == 'AbgabetoolStudent'" :viewData="viewData" :student_uid_prop="student_uid_computed"></AbgabetoolStudent> <AbgabetoolStudent v-if="comp == 'AbgabetoolStudent'" :student_uid_prop="student_uid_computed"></AbgabetoolStudent>
<AbgabetoolMitarbeiter v-if="comp == 'AbgabetoolMitarbeiter'" :viewData="viewData"></AbgabetoolMitarbeiter> <AbgabetoolMitarbeiter v-if="comp == 'AbgabetoolMitarbeiter'"></AbgabetoolMitarbeiter>
<AbgabetoolAssistenz v-if="comp == 'AbgabetoolAssistenz'" :viewData="viewData" :stg_kz_prop="stg_kz_computed"></AbgabetoolAssistenz> <AbgabetoolAssistenz v-if="comp == 'AbgabetoolAssistenz'" :stg_kz_prop="stg_kz_computed"></AbgabetoolAssistenz>
<DeadlineOverview v-if="comp == 'DeadlinesOverview'" :viewData="viewData"></DeadlineOverview> <DeadlineOverview v-if="comp == 'DeadlinesOverview'"></DeadlineOverview>
</template> </template>
` `
}); });
@@ -4,25 +4,27 @@ import Theme from '../../plugins/Theme.js';
import contrast from '../../directives/contrast.js'; import contrast from '../../directives/contrast.js';
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js"; import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
import LvPlan from "../../components/Cis/LvPlan/Lehrveranstaltung.js"; import LvPlan from "../../components/Cis/LvPlan/Lehrveranstaltung.js";
import MyLvPlan from "../../components/Cis/LvPlan/Personal.js"; import MyLvPlan from "../../components/Cis/LvPlan/MyLvPlan.js";
import MylvStudent from "../../components/Cis/Mylv/Student.js"; import MylvStudent from "../../components/Cis/Mylv/Student.js";
import Profil from "../../components/Cis/Profil/Profil.js"; import Profil from "../../components/Cis/Profil/Profil.js";
import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js"; import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js";
import CmsNews from "../../components/Cis/Cms/News.js"; import CmsNews from "../../components/Cis/Cms/News.js";
import CmsContent from "../../components/Cis/Cms/Content.js"; import CmsContent from "../../components/Cis/Cms/Content.js";
import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js"; import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js";
import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../../components/Cis/Mylv/RoomInformation.js"; import RoomInformation, {DEFAULT_MODE_RAUMINFO_DESKTOP, DEFAULT_MODE_RAUMINFO_MOBILE} from "../../components/Cis/Mylv/RoomInformation.js";
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js"; import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js"; import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js"; import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js";
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js"; import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
import Studium from "../../components/Cis/Studium/Studium.js"; 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 ApiRenderers from '../../api/factory/renderers.js';
import ApiRouteInfo from '../../api/factory/routeinfo.js'; import ApiRouteInfo from '../../api/factory/routeinfo.js';
import {capitalize} from "../../helpers/StringHelpers.js"; import {capitalize} from "../../helpers/StringHelpers.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router; const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
const isMobile = window.matchMedia("(max-width: 767px)").matches;
const router = VueRouter.createRouter({ const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(`/${ciPath}`), history: VueRouter.createWebHistory(`/${ciPath}`),
@@ -85,7 +87,7 @@ const router = VueRouter.createRouter({
name: "RoomInformation", name: "RoomInformation",
params: { // in this case always populate other params since they are not optional params: { // in this case always populate other params since they are not optional
ort_kurzbz: to.params.ort_kurzbz, ort_kurzbz: to.params.ort_kurzbz,
mode: DEFAULT_MODE_RAUMINFO, mode: isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP,
focus_date: new Date().toISOString().split("T")[0] focus_date: new Date().toISOString().split("T")[0]
}, },
}; };
@@ -102,7 +104,7 @@ const router = VueRouter.createRouter({
const mode = route.params.mode && const mode = route.params.mode &&
validModes.includes(route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase()) validModes.includes(route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase())
? route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase() ? route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase()
: DEFAULT_MODE_RAUMINFO; : (isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP);
// default to today date if not provided // default to today date if not provided
const d = new Date(route.params.focus_date) const d = new Date(route.params.focus_date)
@@ -197,6 +199,26 @@ const router = VueRouter.createRouter({
}; };
} }
}, },
{
path: `/Cis/StgOrgLvPlan/:mode?/:focus_date?/:stgkz?/:sem?/:verband?/:gruppe?`,
name: 'StgOrgLvPlan',
component: StgOrgLvPlan,
props(route) {
return {
propsViewData: route.params
};
}
},
{
path: `/Cis/OtherLvPlan/:otherUid/:mode?/:focus_date?`,
name: "OtherLvPlan",
component: OtherLvPlan,
props(route) {
return {
propsViewData: route.params
};
}
},
{ {
path: `/Cis4`, path: `/Cis4`,
name: 'Cis4', name: 'Cis4',
@@ -227,26 +249,22 @@ const router = VueRouter.createRouter({
}) })
const app = Vue.createApp({ const app = Vue.createApp({
name: 'FhcApp', name: 'CisApp',
data: () => ({ data: () => ({
appSideMenuEntries: {}, appSideMenuEntries: {},
renderers: null, windowWidth: 0,
}), }),
components: {},
computed: {
isMobile() {
const smallScreen = window.matchMedia("(max-width: 767px)").matches;
const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
return smallScreen;// && touchCapable;
}
},
provide() { provide() {
return { // provide injectable & watchable language property return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language), language: Vue.computed(() => this.$p.user_language),
renderers: Vue.computed(() => this.renderers), isMobile: Vue.computed(() => this.isMobile),
isMobile: this.isMobile
} }
}, },
computed: {
isMobile: function() {
return (this.windowWidth < 767);
}
},
methods: { methods: {
isInternalRoute(href) { isInternalRoute(href) {
const internalBase = window.location.origin const internalBase = window.location.origin
@@ -280,54 +298,21 @@ const app = Vue.createApp({
this.$router.push(route); this.$router.push(route);
} }
} },
handleWindowResize() {
this.windowWidth = window.innerWidth;
},
}, },
async created(){ created() {
this.windowWidth = window.innerWidth;
await this.$api
.call(ApiRenderers.loadRenderers())
.then(res => res.data)
.then(data => {
for (let rendertype of Object.keys(data)) {
let modalTitle = null;
let modalContent = null;
let calendarEvent = null;
if (data[rendertype].modalTitle)
modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalTitle)));
if (data[rendertype].modalContent)
modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalContent)));
if (data[rendertype].calendarEvent)
calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].calendarEvent)));
if (data[rendertype].calendarEventStyles){
var head = document.head;
if(!head.querySelector(`link[href="${data[rendertype].calendarEventStyles}"]`)){
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = data[rendertype].calendarEventStyles;
head.appendChild(link);
}
}
if(this.renderers === null) {
this.renderers = {};
}
if (!this.renderers[rendertype]) {
this.renderers[rendertype] = {}
}
this.renderers[rendertype].modalTitle = modalTitle;
this.renderers[rendertype].modalContent = modalContent;
this.renderers[rendertype].calendarEvent = calendarEvent;
}
});
}, },
mounted() { async mounted() {
document.addEventListener('click', this.handleClick); document.addEventListener('click', this.handleClick);
window.addEventListener("resize", this.handleWindowResize);
}, },
beforeUnmount() { beforeUnmount() {
document.removeEventListener('click', this.handleClick); document.removeEventListener('click', this.handleClick);
window.removeEventListener("resize", this.handleWindowResize);
}, },
}); });
@@ -353,4 +338,4 @@ app.mount('#fhccontent');
router.afterEach((to, from, failure) => { router.afterEach((to, from, failure) => {
app.config.globalProperties.$api.call(ApiRouteInfo.info('cis4', to.fullPath)); app.config.globalProperties.$api.call(ApiRouteInfo.info('cis4', to.fullPath));
}); });
@@ -1,11 +1,12 @@
import FhcSearchbar from "../components/searchbar/searchbar.js"; import FhcSearchbar from "../../components/searchbar/searchbar.js";
import CisMenu from "../components/Cis/Menu.js"; import CisMenu from "../../components/Cis/Menu.js";
import PluginsPhrasen from '../plugins/Phrasen.js'; import PluginsPhrasen from '../../plugins/Phrasen.js';
import ApiSearchbar from '../api/factory/searchbar.js'; import Theme from "../../plugins/Theme.js";
import Theme from "../plugins/Theme.js"; import ApiSearchbar from '../../api/factory/searchbar.js';
import ApiLvPlan from "../../api/factory/lvPlan.js";
const app = Vue.createApp({ const app = Vue.createApp({
name: 'CisApp', name: 'CisMenuApp',
components: { components: {
FhcSearchbar, FhcSearchbar,
CisMenu CisMenu
@@ -133,14 +134,61 @@ const app = Vue.createApp({
childactions: [] childactions: []
} }
} }
} },
windowWidth: 0,
}; };
}, },
provide() {
return {
isNarrow: Vue.computed(() => this.windowWidth < 992),
isMobile: Vue.computed(() => this.windowWidth < 767),
}
},
methods: { methods: {
searchfunction: function(searchsettings) { searchfunction: function(searchsettings) {
return this.$api.call(ApiSearchbar.searchCis(searchsettings)); return this.$api.call(ApiSearchbar.searchCis(searchsettings));
} },
} handleWindowResize() {
this.windowWidth = window.innerWidth;
},
},
created() {
this.windowWidth = window.innerWidth;
},
async mounted() {
const openOtherLvPlanAction = {
label: Vue.computed(() => this.$p.t("lehre/stundenplan")),
icon: "fas fa-calendar-days",
type: "link",
action: function (data) {
const uid = JSON.parse(data.data).uid;
const link =
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
"/Cis/OtherLvPlan/" +
uid;
return link;
},
};
let checkPermissionOtherLvPlanResult = await this.$api.call(
ApiLvPlan.checkPermissionOtherLvPlan(),
);
if (
checkPermissionOtherLvPlanResult.meta.status === "success" &&
checkPermissionOtherLvPlanResult.data
) {
this.searchbaroptions.actions.employee.childactions.push(
openOtherLvPlanAction,
);
this.searchbaroptions.actions.student.childactions.push(
openOtherLvPlanAction,
);
}
window.addEventListener("resize", this.handleWindowResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleWindowResize);
},
}); });
FhcApps.makeExtendable(app); FhcApps.makeExtendable(app);
+7 -45
View File
@@ -3,13 +3,10 @@ import DashboardAdmin from '../../components/Dashboard/Admin.js';
import PluginsPhrasen from '../../plugins/Phrasen.js'; import PluginsPhrasen from '../../plugins/Phrasen.js';
import ApiRenderers from '../../api/factory/renderers.js';
const app = Vue.createApp({ const app = Vue.createApp({
name: 'DashboardAdminApp', name: 'DashboardAdminApp',
data: () => ({ data: () => ({
appSideMenuEntries: {}, appSideMenuEntries: {}
renderers: null
}), }),
components: { components: {
CoreNavigationCmpt, CoreNavigationCmpt,
@@ -17,51 +14,16 @@ const app = Vue.createApp({
}, },
provide() { provide() {
return { return {
// TODO(chris): move those two into the components that need it
renderers: Vue.computed(() => this.renderers),
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone
}; };
},
created() {
this.$api
.call(ApiRenderers.loadRenderers())
.then(res => {
for (let rendertype of Object.keys(res.data)) {
let modalTitle = null;
let modalContent = null;
let calendarEvent = null;
if (res.data[rendertype].modalTitle)
modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalTitle)));
if (res.data[rendertype].modalContent)
modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalContent)));
if (res.data[rendertype].calendarEvent)
calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].calendarEvent)));
if (res.data[rendertype].calendarEventStyles) {
var head = document.head;
if (!head.querySelector(`link[href="${res.data[rendertype].calendarEventStyles}"]`)) {
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = res.data[rendertype].calendarEventStyles;
head.appendChild(link);
}
}
if (this.renderers === null) {
this.renderers = {};
}
if (!this.renderers[rendertype]) {
this.renderers[rendertype] = {}
}
this.renderers[rendertype].modalTitle = modalTitle;
this.renderers[rendertype].modalContent = modalContent;
this.renderers[rendertype].calendarEvent = calendarEvent;
}
})
.catch(this.$fhcAlert.handleSystemErrors);
} }
}); });
app.use(primevue.config.default, {
zIndex: {
overlay: 9000,
tooltip: 8000
}
})
app.use(PluginsPhrasen); app.use(PluginsPhrasen);
app.directive('tooltip', primevue.tooltip); app.directive('tooltip', primevue.tooltip);
app.mount('#main'); app.mount('#main');
-16
View File
@@ -1,16 +0,0 @@
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
import DashboardAdmin from '../components/Dashboard/Admin.js';
import Phrases from "../plugin/Phrasen.js"
Vue.createApp({
name: 'DashboardAdminApp',
data: () => ({
appSideMenuEntries: {}
}),
components: {
CoreNavigationCmpt,
DashboardAdmin
},
mounted() {
}
}).use(Phrases).mount('#main');
+31 -2
View File
@@ -4,7 +4,9 @@ export default {
name: 'BootstrapModal', name: 'BootstrapModal',
data: () => ({ data: () => ({
modal: null, modal: null,
fullscreen: false fullscreen: false,
expandBtnHovered: false,
expandBtnFocused: false,
}), }),
props: { props: {
backdrop: { backdrop: {
@@ -70,6 +72,29 @@ export default {
this.$emit('toggleFullscreen') this.$emit('toggleFullscreen')
} }
}, },
computed: {
getExpandButtonStyles() {
const hovered = this.expandBtnHovered;
const focused = this.expandBtnFocused;
return `display: flex;
align-items: center;
justify-content: center;
width: 1em;
height: 1em;
padding: 0;
border: 0;
background: transparent;
font-size: 1em;
opacity: 0.5;
color: inherit;
cursor: pointer;
line-height: 1;
transition: opacity 0.15s ease;
opacity: ${focused ? '1' : hovered ? '0.75' : '0.5'};
outline: ${focused ? '1px solid currentColor' : 'none'};
outline-offset: 2px;`
}
},
mounted() { mounted() {
if (this.$refs.modal) if (this.$refs.modal)
this.modal = new bootstrap.Modal(this.$refs.modal, { this.modal = new bootstrap.Modal(this.$refs.modal, {
@@ -140,9 +165,13 @@ export default {
<div class="d-flex align-items-center ms-auto gap-2"> <div class="d-flex align-items-center ms-auto gap-2">
<button <button
type="button" type="button"
class="btn mb-1" :style="getExpandButtonStyles"
v-if="allowFullscreenExpand" v-if="allowFullscreenExpand"
@click="toggleFullscreen" @click="toggleFullscreen"
@mouseenter="expandBtnHovered = true"
@mouseleave="expandBtnHovered = false"
@focusin="expandBtnFocused = true"
@focusout="expandBtnFocused = false"
:aria-label="fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'" :aria-label="fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'"
> >
<i v-if="!fullscreen" class="fa-solid fa-expand"></i> <i v-if="!fullscreen" class="fa-solid fa-expand"></i>
@@ -20,7 +20,8 @@ export default {
}, },
inject: { inject: {
mode: "mode", mode: "mode",
dropableEvents: "dropableEvents" dropableEvents: "dropableEvents",
timezone: "timezone"
}, },
props: { props: {
events: Array, events: Array,
+121 -37
View File
@@ -1,5 +1,5 @@
import LineEvent from './Line/Event.js'; import LineEvent from "./Line/Event.js";
import LineBackground from './Line/Background.js'; import LineBackground from "./Line/Background.js";
/** /**
* TODO(chris): * TODO(chris):
@@ -10,54 +10,117 @@ export default {
name: "GridLine", name: "GridLine",
components: { components: {
LineEvent, LineEvent,
LineBackground LineBackground,
},
inject: {
axisRow: "axisRow"
}, },
inject: ["axisRow", "shouldCompactEvents", "compactibleEventTypes"],
props: { props: {
date: { date: {
type: luxon.DateTime, type: luxon.DateTime,
required: true required: true,
}, },
start: { start: {
type: luxon.DateTime, type: luxon.DateTime,
required: true required: true,
}, },
end: { end: {
type: luxon.DateTime, type: luxon.DateTime,
required: true required: true,
}, },
events: { events: {
type: Array, type: Array,
default: [] default: [],
}, },
backgrounds: { backgrounds: {
type: Array, type: Array,
default: [] default: [],
} },
}, },
computed: { computed: {
eventsWithRowInfo() { formattedEvents() {
const events = []; let formattedEvents = this.events.map((event) => {
this.events.forEach(event => { event.rows = [1, -1];
const rows = [1, -1];
if (event.startsHere) { if (event.startsHere) {
rows[0] = 't_' + event.start.diff(this.date).toMillis(); event.rows[0] =
"t_" + event.start.diff(this.date).toMillis();
} }
if (event.endsHere) { if (event.endsHere) {
rows[1] = 't_' + event.end.diff(this.date).toMillis(); event.rows[1] = "t_" + event.end.diff(this.date).toMillis();
} }
events.push({ return event;
...event,
rows
});
}); });
return events;
} if (this.shouldCompactEvents && this.compactibleEventTypes?.length) {
formattedEvents =
this.compactEvents(formattedEvents, this.compactibleEventTypes);
}
return formattedEvents;
},
}, },
template: /* html */` methods: {
compactEvents(events, compactibleEventTypes) {
let formattedEvents = events
.filter(
(event) =>
!compactibleEventTypes.includes(event.type),
)
.map((event) => {
event.display = "default";
return event;
});
let eventsToBeCompacted = events.filter((event) =>
compactibleEventTypes.includes(event.type),
);
let compactedEvents = [];
eventsToBeCompacted.forEach((event) => {
let existingCompactedEvent = compactedEvents.find(
(compactedEvent) =>
event.rows[0] === compactedEvent.rows[0] &&
event.rows[1] === compactedEvent.rows[1],
);
if (!existingCompactedEvent) {
compactedEvents.push({
events: [
{
farbe: event.orig.farbe,
},
],
rows: event.rows,
});
} else {
existingCompactedEvent.events.push({
farbe: event.orig.farbe,
});
}
});
compactedEvents.forEach((compactedEvent) => {
if (compactedEvent.events.length < 4) {
formattedEvents.push({
display: "compacted",
...compactedEvent,
});
} else {
formattedEvents.push({
display: "compacted",
events: compactedEvent.events.slice(0, 3),
rows: compactedEvent.rows,
});
formattedEvents.push({
display: "compactedExtra",
events: compactedEvent.events.slice(3),
rows: compactedEvent.rows,
});
}
});
return formattedEvents;
},
},
template: /* html */ `
<div <div
class="fhc-calendar-base-grid-line" class="fhc-calendar-base-grid-line"
style="position:relative;display:grid;grid-auto-flow:dense" style="position:relative;display:grid;grid-auto-flow:dense"
@@ -69,17 +132,38 @@ export default {
:end="end" :end="end"
:background="bg" :background="bg"
></line-background> ></line-background>
<line-event <template v-for="(event, i) in formattedEvents" :key="i">
v-for="(event, i) in eventsWithRowInfo" <line-event
:key="i" v-if="!event.display || event.display === 'default'"
:style="'grid-' + axisRow + ': ' + event.rows.join('/')" :style="'grid-' + axisRow + ': ' + event.rows.join('/')"
:event="event" :event="event"
> >
<template v-slot="slot"> <template v-slot="slot">
<slot name="event" v-bind="slot" /> <slot name="event" v-bind="slot" />
</template> </template>
</line-event> </line-event>
<div
v-else-if="event.display === 'compacted'"
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
class="d-flex flex-row justify-content-center gap-1 align-items-center"
>
<span
v-for="(subEvent, subEventIndex) in event.events"
:key="subEventIndex"
:style="subEvent.farbe ? {'background-color': '#' + subEvent.farbe} : {}"
style="height:10px; width:10px;"
class="border border-dark rounded-circle"
></span>
</div>
<div
v-else-if="event.display === 'compactedExtra'"
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
class="w-100 d-flex flex-row justify-content-center"
>
{{"+" + event.events.length}}
</div>
</template>
<slot name="dropzone" /> <slot name="dropzone" />
</div> </div>
` `,
} };
+71 -28
View File
@@ -3,24 +3,20 @@ import FhcCalendar from "./Base.js";
import ApiLvPlan from '../../api/factory/lvPlan.js'; import ApiLvPlan from '../../api/factory/lvPlan.js';
import { useEventLoader } from '../../composables/EventLoader.js'; import { useEventLoader } from '../../composables/EventLoader.js';
import { useRenderers } from '../../composables/Renderers.js';
import ModeDay from './Mode/Day.js'; import ModeDay from './Mode/Day.js';
import ModeWeek from './Mode/Week.js'; import ModeWeek from './Mode/Week.js';
import ModeMonth from './Mode/Month.js'; import ModeMonth from './Mode/Month.js';
import ModeList from './Mode/List.js';
export default { export default {
name: "CalendarLvPlan", name: "CalendarLvPlan",
components: { components: {
FhcCalendar FhcCalendar
}, },
inject: [ inject: ["isMobile"],
"renderers"
],
props: { props: {
timezone: {
type: String,
required: true
},
date: { date: {
type: [Date, String, Number, luxon.DateTime], type: [Date, String, Number, luxon.DateTime],
default: luxon.DateTime.local() default: luxon.DateTime.local()
@@ -34,6 +30,16 @@ export default {
required: true required: true
} }
}, },
provide() {
return {
shouldCompactEvents: Vue.computed(
() => this.$props.mode === "Month" && this.isMobile,
),
compactibleEventTypes: Vue.computed(
() => this.compactibleEventTypes,
),
};
},
emits: [ emits: [
"update:date", "update:date",
"update:mode", "update:mode",
@@ -41,11 +47,7 @@ export default {
], ],
data() { data() {
return { return {
modes: { timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
day: Vue.markRaw(ModeDay),
week: Vue.markRaw(ModeWeek),
month: Vue.markRaw(ModeMonth)
},
modeOptions: { modeOptions: {
day: { day: {
emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')), emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')),
@@ -53,9 +55,13 @@ export default {
}, },
week: { week: {
collapseEmptyDays: false collapseEmptyDays: false
} },
list: {
length: 7,
},
}, },
teachingunits: null teachingunits: null,
compactibleEventTypes: [],
}; };
}, },
computed: { computed: {
@@ -77,7 +83,20 @@ export default {
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false }) label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
} }
]; ];
} },
modes() {
let modes = {
day: Vue.markRaw(ModeDay),
month: Vue.markRaw(ModeMonth),
};
if (this.isMobile) {
modes.list = Vue.markRaw(ModeList);
} else {
modes.week = Vue.markRaw(ModeWeek);
}
return modes;
},
}, },
methods: { methods: {
eventStyle(event) { eventStyle(event) {
@@ -88,33 +107,47 @@ export default {
updateRange(rangeInterval) { updateRange(rangeInterval) {
this.rangeInterval = rangeInterval; this.rangeInterval = rangeInterval;
this.$emit('update:range', rangeInterval); this.$emit('update:range', rangeInterval);
} },
resetEventLoader() {
this.reset();
},
async getStunden() {
let stundenResponse = await this.$api.call(ApiLvPlan.getStunden());
this.teachingunits = stundenResponse.data.map((el) => ({
id: el.stunde,
start: el.beginn,
end: el.ende,
}));
},
async getCompactibleEventTypes() {
let compactibleEventTypesResponse = await this.$api.call(
ApiLvPlan.getCompactibleEventTypes(),
);
this.compactibleEventTypes = compactibleEventTypesResponse.data;
},
}, },
setup(props, context) { setup(props, context) {
const rangeInterval = Vue.ref(null); const rangeInterval = Vue.ref(null);
const { events, lv } = useEventLoader(rangeInterval, props.getPromiseFunc); const { events, lv, reset } = useEventLoader(rangeInterval, props.getPromiseFunc);
Vue.watch(lv, newValue => { Vue.watch(lv, newValue => {
context.emit('update:lv', newValue); context.emit('update:lv', newValue);
}); });
const { renderers } = useRenderers();
return { return {
rangeInterval, rangeInterval,
events, events,
lv lv,
reset,
renderers
}; };
}, },
created() { async created() {
this.$api await this.getStunden();
.call(ApiLvPlan.getStunden()) await this.getCompactibleEventTypes();
.then(res => {
return this.teachingunits = res.data.map(el => ({
id: el.stunde,
start: el.beginn,
end: el.ende
}));
});
}, },
template: /* html */` template: /* html */`
<fhc-calendar <fhc-calendar
@@ -136,6 +169,13 @@ export default {
> >
<template v-slot="{ event, mode }"> <template v-slot="{ event, mode }">
<div <div
v-if="!event"
class="h-100 d-flex justify-content-center align-items-center"
>
{{ $p.t('lehre/noLvFound') }}
</div>
<div
v-else
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'" :class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
:type="mode == 'day' ? 'button' : undefined" :type="mode == 'day' ? 'button' : undefined"
:style="eventStyle(event)" :style="eventStyle(event)"
@@ -154,6 +194,9 @@ export default {
v-else v-else
:is="renderers[event.type]?.calendarEvent" :is="renderers[event.type]?.calendarEvent"
:event="event" :event="event"
:timeSlotDisplayBehavior="
$props.mode.toLowerCase() === 'list' ? 'always' : 'default'
"
></component> ></component>
</div> </div>
</template> </template>
+1 -1
View File
@@ -93,7 +93,7 @@ export default {
mounted() { mounted() {
this.$emit('update:range', this.range); this.$emit('update:range', this.range);
}, },
template: ` template: /*html*/ `
<div <div
class="fhc-calendar-mode-list flex-grow-1 position-relative" class="fhc-calendar-mode-list flex-grow-1 position-relative"
@cal-click-default.capture="handleClickDefaults" @cal-click-default.capture="handleClickDefaults"
+8 -9
View File
@@ -1,6 +1,7 @@
import FhcCalendar from "./Base.js"; import FhcCalendar from "./Base.js";
import { useEventLoader } from '../../composables/EventLoader.js'; import { useEventLoader } from '../../composables/EventLoader.js';
import { useRenderers } from '../../composables/Renderers.js';
import ModeList from '../Calendar/Mode/List.js'; import ModeList from '../Calendar/Mode/List.js';
@@ -9,22 +10,17 @@ export default {
components: { components: {
FhcCalendar FhcCalendar
}, },
inject: [
"renderers"
],
props: { props: {
timezone: {
type: String,
required: true
},
getPromiseFunc: { getPromiseFunc: {
type: Function, type: Function,
required: true required: true
} }
}, },
data() { data() {
const timezone = FHC_JS_DATA_STORAGE_OBJECT.timezone;
return { return {
now: luxon.DateTime.now().setZone(this.timezone), timezone,
now: luxon.DateTime.now().setZone(timezone),
modes: { modes: {
list: Vue.markRaw(ModeList) list: Vue.markRaw(ModeList)
}, },
@@ -59,10 +55,12 @@ export default {
const rangeInterval = Vue.ref(null); const rangeInterval = Vue.ref(null);
const { events } = useEventLoader(rangeInterval, props.getPromiseFunc); const { events } = useEventLoader(rangeInterval, props.getPromiseFunc);
const { renderers } = useRenderers();
return { return {
rangeInterval, rangeInterval,
events events,
renderers
}; };
}, },
template: /* html */` template: /* html */`
@@ -99,6 +97,7 @@ export default {
<component <component
:is="renderers[event.type]?.calendarEvent" :is="renderers[event.type]?.calendarEvent"
:event="event" :event="event"
:timeSlotDisplayBehavior="'always'"
></component> ></component>
</div> </div>
</template> </template>
@@ -2,6 +2,8 @@ import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php'; import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiAbgabe from '../../../api/factory/abgabe.js'
import { getDateStyleClass } from "./getDateStyleClass.js"; import { getDateStyleClass } from "./getDateStyleClass.js";
import {compareISODateValues, formatDateTime, formatISODate, getViennaTodayISO} from "./dateUtils.js";
export const AbgabeMitarbeiterDetail = { export const AbgabeMitarbeiterDetail = {
name: "AbgabeMitarbeiterDetail", name: "AbgabeMitarbeiterDetail",
@@ -46,12 +48,7 @@ export const AbgabeMitarbeiterDetail = {
eidAkzeptiert: false, eidAkzeptiert: false,
enduploadTermin: null, enduploadTermin: null,
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages, allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
speedDialItems: [{ speedDialItems: [
label: Vue.computed(() => this.$p.t('abgabetool/c4newAbgabetermin')),
icon: "fa fa-plus",
command: this.openCreateNewAbgabeModal,
disabled: Vue.computed(() => !this.getAllowedToCreateNewTermin)
},
{ {
label: Vue.computed(() => this.$p.t('abgabetool/c4benoten')), label: Vue.computed(() => this.$p.t('abgabetool/c4benoten')),
icon: "fa fa-user-check", icon: "fa fa-user-check",
@@ -81,6 +78,22 @@ export const AbgabeMitarbeiterDetail = {
} }
}, },
methods: { methods: {
getSavedTerminInfoString(termin) {
const isUpdate = termin.updateamum != null;
const fullname = isUpdate
? termin.updatevon_fullname
: termin.insertvon_fullname;
const datetime = isUpdate
? termin.updateamum
: termin.insertamum;
return this.$p.t('ui/savedAtByV3', [formatDateTime(datetime), fullname])
},
terminIsInvalid(termin) {
return termin.note?.positiv == false && !termin.beurteilungsnotiz
},
getNoteBezeichnung(termin){ getNoteBezeichnung(termin){
if(termin.noteBackend?.bezeichnung) { if(termin.noteBackend?.bezeichnung) {
return termin.noteBackend?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌' return termin.noteBackend?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
@@ -113,6 +126,8 @@ export const AbgabeMitarbeiterDetail = {
if(newTerminRes.note) { if(newTerminRes.note) {
newTerminRes.note = noteOpt newTerminRes.note = noteOpt
newTerminRes.noteBackend = noteOpt // certain UI elements should only reflect persisted state newTerminRes.noteBackend = noteOpt // certain UI elements should only reflect persisted state
termin.allowedToDelete = false
newTerminRes.allowedToDelete = false
} }
newTerminRes.invertedFixtermin = !newTerminRes.fixtermin newTerminRes.invertedFixtermin = !newTerminRes.fixtermin
const existingTerminRes = res.data[1] const existingTerminRes = res.data[1]
@@ -132,13 +147,13 @@ export const AbgabeMitarbeiterDetail = {
} else { } else {
const noteOptExisting = this.allowedNotenOptions.find(opt => opt.note == existingTerminRes.note) const noteOptExisting = this.allowedNotenOptions.find(opt => opt.note == existingTerminRes.note)
existingTerminRes.note = noteOptExisting existingTerminRes.note = noteOptExisting
termin.paabgabetyp_kurzbz = newTerminRes.paabgabetyp_kurzbz termin.paabgabetyp_kurzbz = newTerminRes.paabgabetyp_kurzbz
termin.noteBackend = noteOpt // do NOT take noteOptExisting -> should reflect the "yes the qgate grade is confirmed in backend ux behaviour" termin.noteBackend = noteOpt // do NOT take noteOptExisting -> should reflect the "yes the qgate grade is confirmed in backend ux behaviour"
termin.dateStyle = getDateStyleClass(termin, this.notenOptions) termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
} }
this.projektarbeit.abgabetermine.sort((a, b) =>new Date(a.datum) - new Date(b.datum)) this.projektarbeit.abgabetermine.sort((a, b) => compareISODateValues(a.datum, b.datum))
const index = this.projektarbeit.abgabetermine.findIndex(t => termin.paabgabe_id == t.paabgabe_id) const index = this.projektarbeit.abgabetermine.findIndex(t => termin.paabgabe_id == t.paabgabe_id)
@@ -159,7 +174,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false, 'fixtermin': false,
'invertedFixtermin': true, 'invertedFixtermin': true,
'kurzbz': '', // todo kurzbz textfield value vorschlag für qualgates 'kurzbz': '', // todo kurzbz textfield value vorschlag für qualgates
'datum': new Date().toISOString().split('T')[0], 'datum': getViennaTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '', 'beurteilungsnotiz': '',
'upload_allowed': false, 'upload_allowed': false,
@@ -337,16 +352,7 @@ export const AbgabeMitarbeiterDetail = {
} }
}, },
formatDate(dateParam) { formatDate(dateParam) {
// unsafe for datepickers, dont use there return formatISODate(dateParam)
const date = new Date(dateParam)
// handle missing leading 0
const padZero = (num) => String(num).padStart(2, '0');
const month = padZero(date.getMonth() + 1); // Months are zero-based
const day = padZero(date.getDate());
const year = date.getFullYear();
return `${day}.${month}.${year}`
}, },
openCreateNewAbgabeModal() { openCreateNewAbgabeModal() {
if(this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') { if(this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') {
@@ -364,7 +370,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false, 'fixtermin': false,
'invertedFixtermin': true, 'invertedFixtermin': true,
'kurzbz': '', 'kurzbz': '',
'datum': new Date().toISOString().split('T')[0], 'datum': getViennaTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '', 'beurteilungsnotiz': '',
'upload_allowed': typ.upload_allowed_default, 'upload_allowed': typ.upload_allowed_default,
@@ -398,7 +404,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false, 'fixtermin': false,
'invertedFixtermin': true, 'invertedFixtermin': true,
'kurzbz': '', 'kurzbz': '',
'datum': new Date().toISOString().split('T')[0], 'datum': getViennaTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '', 'beurteilungsnotiz': '',
'upload_allowed': false, 'upload_allowed': false,
@@ -446,7 +452,7 @@ export const AbgabeMitarbeiterDetail = {
} }
}, },
getMessagePtStyle() { getMessagePtStyle() {
// adjust outer spacing and internal padding to appear similar to doenload button in size // adjust outer spacing and internal padding to appear similar to download button in size
return { return {
root: { root: {
style: { style: {
@@ -561,6 +567,12 @@ export const AbgabeMitarbeiterDetail = {
class: "custom-tooltip" class: "custom-tooltip"
} }
}, },
getTooltipBeurteilungsnotiz() {
return {
value: this.$p.t('abgabetool/c4beurteilungsnotizBeiNegNote'),
class: "custom-tooltip"
}
},
getProjektarbeitTitel() { getProjektarbeitTitel() {
if(this.projektarbeit?.titel) return this.$capitalize(this.$p.t('abgabetool/c4titel')) + ': ' + this.projektarbeit.titel if(this.projektarbeit?.titel) return this.$capitalize(this.$p.t('abgabetool/c4titel')) + ': ' + this.projektarbeit.titel
@@ -591,7 +603,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false, 'fixtermin': false,
'invertedFixtermin': true, 'invertedFixtermin': true,
'kurzbz': '', 'kurzbz': '',
'datum': new Date().toISOString().split('T')[0], 'datum': getViennaTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '', 'beurteilungsnotiz': '',
'upload_allowed': typ.upload_allowed_default, 'upload_allowed': typ.upload_allowed_default,
@@ -627,6 +639,7 @@ export const AbgabeMitarbeiterDetail = {
dialogClass="bordered-modal modal-lg" dialogClass="bordered-modal modal-lg"
:backdrop="true" :backdrop="true"
@hideBsModal="showAutomagicModalPhrase=false;" @hideBsModal="showAutomagicModalPhrase=false;"
bodyClass="px-4 py-4"
> >
<template v-slot:title> <template v-slot:title>
<div> <div>
@@ -656,6 +669,7 @@ export const AbgabeMitarbeiterDetail = {
:enable-time-picker="false" :enable-time-picker="false"
locale="de" locale="de"
format="dd.MM.yyyy" format="dd.MM.yyyy"
model-type="yyyy-MM-dd"
:text-input="true" :text-input="true"
auto-apply> auto-apply>
</VueDatePicker> </VueDatePicker>
@@ -806,6 +820,7 @@ export const AbgabeMitarbeiterDetail = {
:enable-time-picker="false" :enable-time-picker="false"
locale="de" locale="de"
format="dd.MM.yyyy" format="dd.MM.yyyy"
model-type="yyyy-MM-dd"
:text-input="true" :text-input="true"
auto-apply> auto-apply>
</VueDatePicker> </VueDatePicker>
@@ -816,7 +831,7 @@ export const AbgabeMitarbeiterDetail = {
<div class="col-12 col-md-9"> <div class="col-12 col-md-9">
<Dropdown <Dropdown
:style="{'width': '100%'}" :style="{'width': '100%'}"
:disabled="!termin.allowedToSave" :disabled="!termin.allowedToSave || termin.abgabedatum !== null || termin.noteBackend"
:placeholder="getPlaceholderTermin(termin)" :placeholder="getPlaceholderTermin(termin)"
v-model="termin.bezeichnung" v-model="termin.bezeichnung"
@change="handleChangeAbgabetyp(termin)" @change="handleChangeAbgabetyp(termin)"
@@ -851,8 +866,10 @@ export const AbgabeMitarbeiterDetail = {
</div> </div>
<div class="row mt-2" v-if="termin.bezeichnung?.benotbar"> <div class="row mt-2" v-if="termin.bezeichnung?.benotbar">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div> <div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-12 col-md-9"> <div class="col-12 col-md-9" v-tooltip.right="terminIsInvalid(termin) && getTooltipBeurteilungsnotiz">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" class="w-100" :disabled="!termin.allowedToSave"></Textarea> <Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz"
:class="{ 'p-invalid': terminIsInvalid(termin) }"
rows="1" class="w-100" :disabled="!termin.allowedToSave"></Textarea>
</div> </div>
</div> </div>
@@ -874,6 +891,7 @@ export const AbgabeMitarbeiterDetail = {
:disabled="true" :disabled="true"
locale="de" locale="de"
format="dd.MM.yyyy" format="dd.MM.yyyy"
model-type="yyyy-MM-dd"
> >
</VueDatePicker> </VueDatePicker>
</div> </div>
@@ -889,9 +907,6 @@ export const AbgabeMitarbeiterDetail = {
<Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $capitalize($p.t('abgabetool/c4keineSignatur')) }} </Message> <Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $capitalize($p.t('abgabetool/c4keineSignatur')) }} </Message>
<Message v-else-if="termin?.signatur == 'error'" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $capitalize($p.t('abgabetool/c4signaturServerError')) }} </Message> <Message v-else-if="termin?.signatur == 'error'" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $capitalize($p.t('abgabetool/c4signaturServerError')) }} </Message>
</div> </div>
<!-- <div v-else class="col-auto">-->
<!-- <Message severity="info" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4noFileFound') }} </Message>-->
<!-- </div>-->
</div> </div>
</template> </template>
@@ -904,10 +919,10 @@ export const AbgabeMitarbeiterDetail = {
<div class="col-12 col-md-3 fw-bold align-content-center"> <div class="col-12 col-md-3 fw-bold align-content-center">
{{ $capitalize( $p.t('abgabetool/c4actions') )}} {{ $capitalize( $p.t('abgabetool/c4actions') )}}
</div> </div>
<div class="col-12 col-md-9"> <div class="col-12 col-md-5">
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
<button v-if="termin.allowedToSave" style="max-height: 40px;" class="btn btn-primary border-0" @click="saveTermin(termin)"> <button v-if="termin.allowedToSave && !terminIsInvalid(termin)" style="max-height: 40px;" class="btn btn-primary border-0" @click="saveTermin(termin)">
{{ $capitalize( $p.t('abgabetool/c4save') )}} {{ $capitalize( $p.t('abgabetool/c4save') )}}
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk"></i>
</button> </button>
@@ -932,6 +947,9 @@ export const AbgabeMitarbeiterDetail = {
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-md-4 align-content-center text-end text-muted small">
{{getSavedTerminInfoString(termin)}}
</div>
</div> </div>
</AccordionTab> </AccordionTab>
</template> </template>
@@ -944,7 +962,8 @@ export const AbgabeMitarbeiterDetail = {
<bs-modal <bs-modal
ref="modalContainerZusatzdaten" ref="modalContainerZusatzdaten"
class="bootstrap-prompt" class="bootstrap-prompt"
dialogClass="bordered-modal modal-lg"> dialogClass="bordered-modal modal-lg"
bodyClass="px-4 py-4">
<template v-slot:title> <template v-slot:title>
<div> <div>
{{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}} {{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}}
@@ -3,6 +3,9 @@ import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php'; import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { formatISODate, getViennaTodayISO } from "./dateUtils.js";
import { validateThesisTitle } from './titleValidation.js'
export const AbgabeStudentDetail = { export const AbgabeStudentDetail = {
name: "AbgabeStudentDetail", name: "AbgabeStudentDetail",
@@ -20,7 +23,16 @@ export const AbgabeStudentDetail = {
VueDatePicker, VueDatePicker,
FhcOverlay FhcOverlay
}, },
inject: ['notenOptions', 'isMobile', 'isViewMode', 'moodle_link'], inject: [
'notenOptions',
'isMobile',
'isViewMode',
'moodle_link',
'confetti_on_endupload',
'title_edit_allowed',
'siginfolink_german',
'siginfolink_english'
],
props: { props: {
projektarbeit: { projektarbeit: {
type: Object, type: Object,
@@ -31,12 +43,14 @@ export const AbgabeStudentDetail = {
default: false default: false
} }
}, },
emits: ['titel-updated'],
data() { data() {
return { return {
loading: false, loading: false,
eidAkzeptiert: false, eidAkzeptiert: false,
enduploadTermin: null, enduploadTermin: null,
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages, allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
editingTitel: '',
form: Vue.reactive({ form: Vue.reactive({
sprache: '', sprache: '',
abstract: '', abstract: '',
@@ -49,9 +63,120 @@ export const AbgabeStudentDetail = {
} }
}, },
methods: { methods: {
confettiCannons() {
const container = document.getElementById('confetti-container');
if (!container) return;
const colors = ['#FFC107', '#FF5722', '#E91E63', '#00BCD4', '#4CAF50', '#9C27B0'];
const shapes = ['50%', '0%'];
const fragment = document.createDocumentFragment();
// Corner Cannons - Slowed Down)
const cannonCount = 150;
for (let i = 0; i < cannonCount; i++) {
const leftConfetti = document.createElement('div');
leftConfetti.classList.add('confetti-piece');
leftConfetti.style.left = '0px';
leftConfetti.style.top = '100%';
const rightConfetti = document.createElement('div');
rightConfetti.classList.add('confetti-piece');
rightConfetti.style.left = '100vw';
rightConfetti.style.top = '100%';
const colorL = colors[Math.floor(Math.random() * colors.length)];
const colorR = colors[Math.floor(Math.random() * colors.length)];
const shapeL = shapes[Math.floor(Math.random() * shapes.length)];
const shapeR = shapes[Math.floor(Math.random() * shapes.length)];
// Left Styles
leftConfetti.style.background = colorL;
leftConfetti.style.borderRadius = shapeL;
leftConfetti.style.width = `${Math.random() * 10 + 6}px`;
leftConfetti.style.height = `${Math.random() * 14 + 6}px`;
leftConfetti.style.setProperty('--blast-x', `${Math.random() * 50 + 10}vw`);
leftConfetti.style.setProperty('--blast-y', `-${Math.random() * 65 + 30}vh`);
// Right Styles
rightConfetti.style.background = colorR;
rightConfetti.style.borderRadius = shapeR;
rightConfetti.style.width = `${Math.random() * 10 + 6}px`;
rightConfetti.style.height = `${Math.random() * 14 + 6}px`;
rightConfetti.style.setProperty('--blast-x', `-${Math.random() * 50 + 10}vw`);
rightConfetti.style.setProperty('--blast-y', `-${Math.random() * 65 + 30}vh`);
// Increased durations to 3s - 5s for a floating gravity effect
const durationL = Math.random() * 2 + 3;
const durationR = Math.random() * 2 + 3;
const delayL = Math.random() * 0.2;
const delayR = Math.random() * 0.2;
leftConfetti.style.animation = `cannonBlast ${durationL}s linear ${delayL}s both`;
rightConfetti.style.animation = `cannonBlast ${durationR}s linear ${delayR}s both`;
fragment.appendChild(leftConfetti);
fragment.appendChild(rightConfetti);
setTimeout(() => leftConfetti.remove(), (delayL + durationL) * 1000);
setTimeout(() => rightConfetti.remove(), (delayR + durationR) * 1000);
}
container.appendChild(fragment);
},
openTitelEdit() {
this.editingTitel = this.projektarbeit.titel ?? '';
this.$refs.modalTitelEdit.show();
},
async saveTitel() {
const validation = validateThesisTitle(this.editingTitel);
if (!validation.isValid) {
if (validation.error === 'empty') {
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4emptyThesisTitle'))
} else if (validation.error === 'invalid_characters') {
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4invalidCharactersThesisTitle'))
}
return false;
}
const confirmed = await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/c4confirmTitelSpeichern'),
acceptLabel: this.$capitalize(this.$p.t('ui/speichern')),
acceptClass: 'p-button-primary',
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'p-button-secondary'
});
if (confirmed === false) return;
this.loading = true;
this.$api.call(
ApiAbgabe.postStudentProjektarbeitTitel(
this.projektarbeit.projektarbeit_id,
validation.cleanedTitle
)
).then(res => {
if (res.meta.status === 'success') {
this.projektarbeit.titel = res.data;
this.$emit('titel-updated', {
projektarbeit_id: this.projektarbeit.projektarbeit_id,
titel: res.data
});
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess')));
this.$refs.modalTitelEdit.hide();
} else {
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4titelSaveError')));
}
}).finally(() => {
this.loading = false;
});
},
getNoteBezeichnung(termin){ getNoteBezeichnung(termin){
const noteOpt = this.notenOptions.find(opt => opt.note == termin.note) const noteOpt = this.notenOptions.find(opt => opt.note == termin.note)
if(noteOpt?.bezeichnung) { if(noteOpt?.bezeichnung) {
return noteOpt?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌' return noteOpt?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
} else if(noteOpt?.benotbar === true && !termin.note) { } else if(noteOpt?.benotbar === true && !termin.note) {
@@ -65,7 +190,7 @@ export const AbgabeStudentDetail = {
this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningChooseFile'))); this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningChooseFile')));
return false return false
} }
if(endupload) { if(endupload) {
if(await this.$fhcAlert.confirm({ if(await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/confirmEnduploadSpeichern'), message: this.$p.t('abgabetool/confirmEnduploadSpeichern'),
@@ -77,16 +202,16 @@ export const AbgabeStudentDetail = {
return false return false
} }
} }
return true; return true;
}, },
async triggerEndupload() { async triggerEndupload() {
if (!await this.validate(this.enduploadTermin, true)) if (!await this.validate(this.enduploadTermin, true))
{ {
return false; return false;
} }
// post endabgabe // post endabgabe
const formData = new FormData(); const formData = new FormData();
formData.append('paabgabetyp_kurzbz', this.enduploadTermin.paabgabetyp_kurzbz) formData.append('paabgabetyp_kurzbz', this.enduploadTermin.paabgabetyp_kurzbz)
@@ -94,14 +219,14 @@ export const AbgabeStudentDetail = {
formData.append('paabgabe_id', this.enduploadTermin.paabgabe_id) formData.append('paabgabe_id', this.enduploadTermin.paabgabe_id)
formData.append('student_uid', this.projektarbeit.student_uid) formData.append('student_uid', this.projektarbeit.student_uid)
formData.append('bperson_id', this.projektarbeit.bperson_id) formData.append('bperson_id', this.projektarbeit.bperson_id)
formData.append('sprache', this.form['sprache'].sprache) formData.append('sprache', this.form['sprache'].sprache)
formData.append('abstract', this.form['abstract']) formData.append('abstract', this.form['abstract'])
formData.append('abstract_en', this.form['abstract_en']) formData.append('abstract_en', this.form['abstract_en'])
formData.append('schlagwoerter', this.form['schlagwoerter']) formData.append('schlagwoerter', this.form['schlagwoerter'])
formData.append('schlagwoerter_en', this.form['schlagwoerter_en']) formData.append('schlagwoerter_en', this.form['schlagwoerter_en'])
formData.append('seitenanzahl', this.form['seitenanzahl']) formData.append('seitenanzahl', this.form['seitenanzahl'])
for (let i = 0; i < this.enduploadTermin.file.length; i++) { for (let i = 0; i < this.enduploadTermin.file.length; i++) {
formData.append('file', this.enduploadTermin.file[i]); formData.append('file', this.enduploadTermin.file[i]);
} }
@@ -109,39 +234,31 @@ export const AbgabeStudentDetail = {
this.$api.call(ApiAbgabe.postStudentProjektarbeitEndupload(formData)) this.$api.call(ApiAbgabe.postStudentProjektarbeitEndupload(formData))
.then(res => { .then(res => {
this.handleUploadRes(res, this.enduploadTermin) this.handleUploadRes(res, this.enduploadTermin)
if(this.confetti_on_endupload && res.meta.status == "success") {
this.confettiCannons()
}
}).finally(()=> { }).finally(()=> {
this.loading = false this.loading = false
}) })
this.$refs.modalContainerEnduploadZusatzdaten.hide() this.$refs.modalContainerEnduploadZusatzdaten.hide()
}, },
downloadAbgabe(termin) { downloadAbgabe(termin) {
const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${termin.paabgabe_id}&student_uid=${this.projektarbeit.student_uid}&projektarbeit_id=${this.projektarbeit.projektarbeit_id}`; const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${termin.paabgabe_id}&student_uid=${this.projektarbeit.student_uid}&projektarbeit_id=${this.projektarbeit.projektarbeit_id}`;
window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url) window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url)
// this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
}, },
formatDate(dateParam) { formatDate(dateParam) {
const date = new Date(dateParam) return formatISODate(dateParam)
// handle missing leading 0
const padZero = (num) => String(num).padStart(2, '0');
const month = padZero(date.getMonth() + 1); // Months are zero-based
const day = padZero(date.getDate());
const year = date.getFullYear();
return `${day}.${month}.${year}`
}, },
async upload(termin) { async upload(termin) {
// only do this on endupload
if (! await this.validate(termin)) if (! await this.validate(termin))
{ {
return false; return false;
} }
if(termin.bezeichnung?.paabgabetyp_kurzbz === 'end') { if(termin.bezeichnung?.paabgabetyp_kurzbz === 'end') {
// open endupload form modal for further inputs
this.enduploadTermin = termin this.enduploadTermin = termin
this.$refs.modalContainerEnduploadZusatzdaten.show() this.$refs.modalContainerEnduploadZusatzdaten.show()
} else { } else {
@@ -151,7 +268,7 @@ export const AbgabeStudentDetail = {
formData.append('paabgabe_id', termin.paabgabe_id) formData.append('paabgabe_id', termin.paabgabe_id)
formData.append('student_uid', this.projektarbeit.student_uid) formData.append('student_uid', this.projektarbeit.student_uid)
formData.append('bperson_id', this.projektarbeit.bperson_id) formData.append('bperson_id', this.projektarbeit.bperson_id)
for (let i = 0; i < termin.file.length; i++) { for (let i = 0; i < termin.file.length; i++) {
formData.append('file', termin.file[i]); formData.append('file', termin.file[i]);
} }
@@ -161,7 +278,7 @@ export const AbgabeStudentDetail = {
.then(res => { .then(res => {
this.handleUploadRes(res, termin) this.handleUploadRes(res, termin)
}).finally(()=> { }).finally(()=> {
this.loading = false this.loading = false
}) })
} }
}, },
@@ -169,21 +286,18 @@ export const AbgabeStudentDetail = {
if(res.meta.status == "success") { if(res.meta.status == "success") {
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4fileUploadSuccessv3'))) this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4fileUploadSuccessv3')))
// update 'abgabedatum' for successful upload -> shows the pdf icon and date once set termin.abgabedatum = getViennaTodayISO();
termin.abgabedatum = new Date().toISOString().split('T')[0];
if(res?.data?.signatur !== undefined) { if(res?.data?.signatur !== undefined) {
termin.signatur = res.data.signatur termin.signatur = res.data.signatur
} }
} else { } else {
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4fileUploadErrorv3'))) this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4fileUploadErrorv3')))
} }
if(res.meta.signaturInfo) { if(res.meta.signaturInfo) {
this.$fhcAlert.alertInfo(res.meta.signaturInfo) this.$fhcAlert.alertInfo(res.meta.signaturInfo)
} }
}, },
getOptionLabel(option) { getOptionLabel(option) {
return option.sprache return option.sprache
@@ -195,7 +309,6 @@ export const AbgabeStudentDetail = {
}, },
watch: { watch: {
projektarbeit(newVal) { projektarbeit(newVal) {
// default select german if projektarbeit sprache was null
this.form.sprache = newVal.sprache ? this.allActiveLanguages.find(lang => lang.sprache == newVal.sprache) : this.allActiveLanguages.find(lang => lang.sprache == 'German') this.form.sprache = newVal.sprache ? this.allActiveLanguages.find(lang => lang.sprache == newVal.sprache) : this.allActiveLanguages.find(lang => lang.sprache == 'German')
this.form.abstract = newVal.abstract ?? '' this.form.abstract = newVal.abstract ?? ''
this.form.abstract_en = newVal.abstract_en ?? '' this.form.abstract_en = newVal.abstract_en ?? ''
@@ -203,15 +316,22 @@ export const AbgabeStudentDetail = {
this.form.schlagwoerter_en = newVal.schlagwoerter_en ?? '' this.form.schlagwoerter_en = newVal.schlagwoerter_en ?? ''
this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter ?? '' this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter ?? ''
this.form.seitenanzahl = newVal.seitenanzahl ?? 1 this.form.seitenanzahl = newVal.seitenanzahl ?? 1
} }
}, },
computed: { computed: {
getSignaturInfoLink() {
if(this.$p.user_language.value == 'German' && this.siginfolink_german) return this.siginfolink_german
else if (this.$p.user_language.value == 'English' && this.siginfolink_english) return this.siginfolink_english
},
getSignaturInfoAvailable() {
if(this.$p.user_language.value == 'German' && this.siginfolink_german) return true
else if (this.$p.user_language.value == 'English' && this.siginfolink_english) return true
else return false
},
getMoodleLink() { getMoodleLink() {
return this.moodle_link + this.projektarbeit.studiengang_kz return this.moodle_link + this.projektarbeit.studiengang_kz
}, },
getMessagePtStyle() { getMessagePtStyle() {
// adjust outer spacing and internal padding to appear similar to doenload button in size
return { return {
root: { root: {
style: { style: {
@@ -244,102 +364,84 @@ export const AbgabeStudentDetail = {
}) })
return qgatefound return qgatefound
}, },
isTitelEditAllowed() {
return this.title_edit_allowed && !this.isViewMode && !this.projektarbeit?.note;
},
getTooltipVerspaetet() { getTooltipVerspaetet() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerspaetet')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerspaetet')),
class: "custom-tooltip"
}
}, },
getTooltipVerpasst() { getTooltipVerpasst() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerpasst')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerpasst')),
class: "custom-tooltip"
}
}, },
getTooltipAbzugeben() { getTooltipAbzugeben() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbzugeben')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbzugeben')),
class: "custom-tooltip"
}
}, },
getTooltipStandard() { getTooltipStandard() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipStandardv2')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipStandardv2')),
class: "custom-tooltip"
}
}, },
getTooltipAbgegeben() { getTooltipAbgegeben() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgegeben')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgegeben')),
class: "custom-tooltip"
}
}, },
getTooltipFixtermin() { getTooltipFixtermin() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipFixtermin')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipFixtermin')),
class: "custom-tooltip"
}
}, },
getTooltipAbgabeDetected() { getTooltipAbgabeDetected() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgabeDetected')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgabeDetected')),
class: "custom-tooltip"
}
}, },
getTooltipNotAllowedToUpload() { getTooltipNotAllowedToUpload() {
if(this.isViewMode) { if(this.isViewMode) {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedInViewMode')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedInViewMode')),
class: "custom-tooltip"
}
} else { } else {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedRegularv2')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedRegular')),
class: "custom-tooltip"
}
} }
}, },
getTooltipBeurteilungerforderlich() { getTooltipBeurteilungerforderlich() {
return { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich')), class: "custom-tooltip" }
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich')),
class: "custom-tooltip"
}
}, },
getTooltipBestanden() { getTooltipBestanden() {
return { return { value: this.$p.t('abgabetool/c4tooltipBestanden'), class: "custom-tooltip" }
value: this.$p.t('abgabetool/c4tooltipBestanden'),
class: "custom-tooltip"
}
}, },
getTooltipNichtBestanden() { getTooltipNichtBestanden() {
return { return { value: this.$p.t('abgabetool/c4tooltipNichtBestanden'), class: "custom-tooltip" }
value: this.$p.t('abgabetool/c4tooltipNichtBestanden'),
class: "custom-tooltip"
}
}, },
},
created() {
},
mounted() {
}, },
template: ` template: `
<FhcOverlay :active="loading"></FhcOverlay> <FhcOverlay :active="loading"></FhcOverlay>
<div v-if="projektarbeit"> <div v-if="projektarbeit">
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereich') )}}</h5> <h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereichv2') )}}</h5>
<div class="row"> <div class="row">
<div class="col-8"> <div class="col-8">
<p> {{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}</p> <p>{{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}</p>
<p> {{$capitalize( $p.t('abgabetool/c4titel') ) }}: {{projektarbeit?.titel}}</p>
<p> {{$capitalize( $p.t('abgabetool/c4betreuerv2') ) }}: {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}</p> <p class="d-flex align-items-center gap-2 mb-2" style="min-width: 0;">
<span
:title="projektarbeit.titel"
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 480px;"
>{{$capitalize( $p.t('abgabetool/c4titel') ) }}: {{projektarbeit?.titel}}</span>
<button
v-if="isTitelEditAllowed"
class="btn btn-sm btn-outline-secondary border-0 p-1"
v-tooltip.right="{ value: $capitalize($p.t('abgabetool/c4titelBearbeiten')), class: 'custom-tooltip' }"
@click="openTitelEdit"
>
<i class="fa-solid fa-pen"></i>
</button>
</p>
<p>{{$capitalize( $p.t('abgabetool/c4betreuerv2') ) }}: {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}</p>
</div> </div>
<div class="col-4"> <div class="col-4">
<p>{{ $p.t('abgabetool/c4checkoutStgMoodleInfos') }} <div class="row">
<a :href="getMoodleLink" target="_blank">Moodle</a> <p>{{ $p.t('abgabetool/c4checkoutStgMoodleInfos') }}
</p> <a :href="getMoodleLink" target="_blank">Moodle</a>
</p>
</div>
<div class="row" v-if="getSignaturInfoAvailable">
<a :href="getSignaturInfoLink" target="_blank">{{$p.t('abgabetool/c4signaturinfo')}} <i class="fa-solid fa-circle-info"></i></a>
</div>
</div> </div>
</div> </div>
@@ -357,7 +459,6 @@ export const AbgabeStudentDetail = {
<i v-else-if="termin.dateStyle == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i> <i v-else-if="termin.dateStyle == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>
<i v-else-if="termin.dateStyle == 'bestanden'" v-tooltip.right="getTooltipBestanden" class="fa-solid fa-check"></i> <i v-else-if="termin.dateStyle == 'bestanden'" v-tooltip.right="getTooltipBestanden" class="fa-solid fa-check"></i>
<i v-else-if="termin.dateStyle == 'nichtbestanden'" v-tooltip.right="getTooltipNichtBestanden" class="fa-solid fa-circle-exclamation"></i> <i v-else-if="termin.dateStyle == 'nichtbestanden'" v-tooltip.right="getTooltipNichtBestanden" class="fa-solid fa-circle-exclamation"></i>
</div> </div>
<div class="text-start px-2" style="min-width: 150px; max-width: 300px; margin-left: 40px"> <div class="text-start px-2" style="min-width: 150px; max-width: 300px; margin-left: 40px">
<span>{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}</span> <span>{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}</span>
@@ -408,8 +509,6 @@ export const AbgabeStudentDetail = {
</div> </div>
</template> </template>
</Inplace> </Inplace>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
@@ -430,7 +529,7 @@ export const AbgabeStudentDetail = {
</VueDatePicker> </VueDatePicker>
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}</div> <div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}</div>
<div class="col-12 col-md-9"> <div class="col-12 col-md-9">
@@ -466,7 +565,7 @@ export const AbgabeStudentDetail = {
<div class="col-12 col-md-9"> <div class="col-12 col-md-9">
<template v-if="termin?.abgabedatum"> <template v-if="termin?.abgabedatum">
<div class="row"> <div class="row">
<div style="width:100px; align-content: center;"> <div style="width:100px; align-content: end;">
<h6>{{ termin.abgabedatum?.split("-").reverse().join(".") }}</h6> <h6>{{ termin.abgabedatum?.split("-").reverse().join(".") }}</h6>
</div> </div>
@@ -481,9 +580,6 @@ export const AbgabeStudentDetail = {
<Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4keineSignatur') }} </Message> <Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4keineSignatur') }} </Message>
<Message v-else-if="termin?.signatur == 'error'" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturServerError') }} </Message> <Message v-else-if="termin?.signatur == 'error'" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturServerError') }} </Message>
</div> </div>
<!-- <div v-else class="col-auto">-->
<!-- <Message severity="info" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4noFileFound') }} </Message>-->
<!-- </div>-->
</template> </template>
</div> </div>
</template> </template>
@@ -542,25 +638,66 @@ export const AbgabeStudentDetail = {
<h5>{{ $capitalize( $p.t('abgabetool/c4keineAbgabetermineGefunden') )}}</h5> <h5>{{ $capitalize( $p.t('abgabetool/c4keineAbgabetermineGefunden') )}}</h5>
</div> </div>
</div> </div>
<div v-if="confetti_on_endupload" id="confetti-container"></div>
<bs-modal
ref="modalTitelEdit"
class="bootstrap-prompt"
dialogClass="bordered-modal"
bodyClass="px-4 py-4"
>
<template v-slot:title>
{{$capitalize( $p.t('abgabetool/c4titelBearbeiten') )}}
</template>
<template v-slot:default>
<div class="mb-2">
<label class="form-label fw-bold">
{{$capitalize( $p.t('abgabetool/c4titel') )}}
</label>
<Textarea
v-model="editingTitel"
rows="2"
maxlength="1024"
class="form-control w-100"
@keydown.enter.prevent="saveTitel"
/>
<div class="form-text text-end">{{ editingTitel.length }} / 1024</div>
</div>
</template>
<template v-slot:footer>
<button
class="btn btn-secondary"
@click="$refs.modalTitelEdit.hide()"
>
{{$capitalize( $p.t('abgabetool/c4Cancel') )}}
</button>
<button
class="btn btn-primary"
:disabled="!editingTitel.trim()"
@click="saveTitel"
>
<i class="fa-solid fa-floppy-disk me-1"></i>
{{$capitalize( $p.t('ui/speichern') )}}
</button>
</template>
</bs-modal>
<bs-modal <bs-modal
ref="modalContainerEnduploadZusatzdaten" ref="modalContainerEnduploadZusatzdaten"
class="bootstrap-prompt" class="bootstrap-prompt"
dialogClass="bordered-modal modal-lg"> dialogClass="bordered-modal modal-lg"
bodyClass="px-4 py-4">
<template v-slot:title> <template v-slot:title>
<div> <div>
{{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}} {{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}}
</div> </div>
<div class="row mb-3 align-items-start"> <div class="row mb-3 align-items-start">
<p class="ml-4 mr-4">Student UID: {{ projektarbeit?.student_uid}}</p> <p class="ml-4 mr-4">Student UID: {{ projektarbeit?.student_uid}}</p>
</div> </div>
<div class="row mb-3 align-items-start"> <div class="row mb-3 align-items-start">
<p class="ml-4 mr-4">{{$capitalize( $p.t('abgabetool/c4titel') )}}: {{ projektarbeit?.titel }}</p> <p class="ml-4 mr-4">{{$capitalize( $p.t('abgabetool/c4titel') )}}: {{ projektarbeit?.titel }}</p>
</div> </div>
</template> </template>
<template v-slot:default> <template v-slot:default>
@@ -576,15 +713,6 @@ export const AbgabeStudentDetail = {
</div> </div>
</div> </div>
<!-- lektor fills these out-->
<!-- <div class="row mb-3 align-items-start">-->
<!-- <div class="row">Kontrollierte Schlagwörter</div>-->
<!-- <div class="row">-->
<!-- <Textarea v-model="form.kontrollschlagwoerter"></Textarea>-->
<!-- </div>-->
<!-- -->
<!-- -->
<!-- </div>-->
<div class="row mb-3 align-items-start"> <div class="row mb-3 align-items-start">
<div class="row">{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}</div> <div class="row">{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}</div>
<div class="row"> <div class="row">
@@ -631,7 +759,6 @@ export const AbgabeStudentDetail = {
<div class="col-9"></div> <div class="col-9"></div>
<div class="col-2"><p>{{$capitalize( $p.t('abgabetool/c4gelesenUndAkzeptiert') )}}</p></div> <div class="col-2"><p>{{$capitalize( $p.t('abgabetool/c4gelesenUndAkzeptiert') )}}</p></div>
<div class="col-1"> <div class="col-1">
<Checkbox <Checkbox
v-model="eidAkzeptiert" v-model="eidAkzeptiert"
:binary="true" :binary="true"
@@ -647,8 +774,8 @@ export const AbgabeStudentDetail = {
<div v-show="!allowedToSaveZusatzdaten">{{ $p.t('abgabetool/c4zusatzdatenausfuellen') }}</div> <div v-show="!allowedToSaveZusatzdaten">{{ $p.t('abgabetool/c4zusatzdatenausfuellen') }}</div>
<button class="btn btn-primary" :disabled="!getAllowedToSendEndupload" @click="triggerEndupload">{{$capitalize( $p.t('ui/hochladen') )}}</button> <button class="btn btn-primary" :disabled="!getAllowedToSendEndupload" @click="triggerEndupload">{{$capitalize( $p.t('ui/hochladen') )}}</button>
</template> </template>
</bs-modal> </bs-modal>
`, `,
}; };
@@ -0,0 +1,251 @@
import AbgabeterminStatusLegende from "./StatusLegende.js";
import {formatDateTime} from "./dateUtils.js";
export const AbgabeStudentTimeline = {
name: "AbgabeStudentTimeline",
components: {
AbgabeterminStatusLegende,
Timeline: primevue.timeline,
},
props: {
projekte: { type: Array, default: () => [] },
notenOptions: { type: Array, default: () => [] },
formatDateFn: { type: Function, required: true }
},
data() {
return {
legendExpanded: false,
expandedProjects: {}
}
},
computed: {
student() {
return this.projekte?.[0] ?? null
}
},
watch: {
projekte: {
immediate: true,
handler(val) {
// open all projects by default whenever the student changes
const state = {}
val?.forEach(p => { state[p.projektarbeit_id] = true })
this.expandedProjects = state
this.legendExpanded = false
}
}
},
methods: {
getNoteBezeichnung(projektarbeit) {
if(projektarbeit.note && this.notenOptions) {
const noteOpt = this.notenOptions.find(opt => opt.note == projektarbeit.note)
return noteOpt?.bezeichnung
} else {
return ''
}
},
getSavedTerminInfoString(termin) {
const isUpdate = termin.updateamum != null;
const fullname = isUpdate
? termin.updatevon_fullname
: termin.insertvon_fullname;
const datetime = isUpdate
? termin.updateamum
: termin.insertamum;
return this.$p.t('ui/savedAtByV3', [formatDateTime(datetime), fullname])
},
getItemBezeichnung(item) {
if (!item?.bezeichnung) return ''
return item.bezeichnung?.bezeichnung ?? item.bezeichnung
},
getItemNote(item) {
if (!item?.note) return ''
if (item.note?.bezeichnung) return item.note.bezeichnung
return this.notenOptions?.find(n => n.note == item.note)?.bezeichnung ?? String(item.note)
},
getIconClass(dateStyle) {
return ({
verspaetet: 'fa-solid fa-triangle-exclamation',
verpasst: 'fa-solid fa-calendar-xmark',
abzugeben: 'fa-solid fa-hourglass-half',
standard: 'fa-solid fa-clock',
abgegeben: 'fa-solid fa-paperclip',
beurteilungerforderlich: 'fa-solid fa-list-check',
bestanden: 'fa-solid fa-check',
nichtbestanden: 'fa-solid fa-circle-exclamation',
})[dateStyle] ?? ''
},
getBetreuerLabel(projekt) {
return projekt.erstbetreuer_full_name
|| (projekt.betreuer_vorname ? `${projekt.betreuer_vorname} ${projekt.betreuer_nachname}`.trim() : null)
},
toggleProject(id) {
this.expandedProjects[id] = !this.expandedProjects[id]
}
},
template: `
<div v-if="student">
<div class="d-flex align-items-baseline gap-2 mb-3 pb-2 border-bottom">
<span class="fw-bold fs-6">{{ student.student_vorname }} {{ student.student_nachname }}</span>
<span class="text-muted small">{{ student.student_uid }}</span>
<span v-if="student.matrikelnr" class="text-muted small ms-auto">{{ student.matrikelnr }}</span>
</div>
<div v-for="projekt in projekte" :key="projekt.projektarbeit_id" class="mb-2">
<button
class="btn btn-sm w-100 text-start d-flex align-items-center gap-2 rounded bg-light border-0 py-2 px-3"
@click="toggleProject(projekt.projektarbeit_id)"
>
<i
:class="expandedProjects[projekt.projektarbeit_id] ? 'fa-solid fa-chevron-down' : 'fa-solid fa-chevron-right'"
style="width: 12px; flex-shrink: 0;"
></i>
<span class="fw-semibold text-truncate flex-grow-1" :title="projekt.titel">
{{ projekt.titel || projekt.projekttyp_kurzbz || projekt.projektarbeit_id }}
</span>
<span
v-if="projekt.note"
class="small fw-semibold mx-2 flex-shrink-0"
>
{{ getNoteBezeichnung(projekt) }}
</span>
<span class="text-muted small me-1 flex-shrink-0">{{ projekt.studiensemester_kurzbz }}</span>
<span class="badge bg-secondary flex-shrink-0">{{ projekt.abgabetermine?.length ?? 0 }}</span>
</button>
<div v-show="expandedProjects[projekt.projektarbeit_id]" class="px-2 pt-1">
<div v-if="getBetreuerLabel(projekt)" class="text-muted small px-1 mb-2">
{{ projekt.betreuerart || $capitalize($p.t('abgabetool/c4erstbetreuerv2')) }}:
{{ getBetreuerLabel(projekt) }}
</div>
<Timeline :value="projekt.abgabetermine" align="right">
<template #marker="slotProps">
<!-- padding:0 overrides the 34px accordion left-padding from the -header CSS class -->
<div
:class="slotProps.item.dateStyle + '-header'"
style="height: 26px; width: 26px; padding: 0; border-radius: 4px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;"
>
<i :class="getIconClass(slotProps.item.dateStyle)" style="font-size: 0.75rem;"></i>
</div>
</template>
<template #opposite="slotProps">
<div class="text-end small text-nowrap text-muted">
{{ formatDateFn(slotProps.item.datum) }}
</div>
</template>
<template #content="slotProps">
<div class="small pb-1">
<div class="d-flex align-items-center gap-1 fw-semibold">
<div
:class="slotProps.item.dateStyle + '-header'"
style="height:20px;width:20px;padding:0;border-radius:3px;display:flex;align-items:center;justify-content:center;flex-shrink:0;"
>
<i :class="getIconClass(slotProps.item.dateStyle)" style="font-size:.65rem;"></i>
</div>
<span>
{{ getItemBezeichnung(slotProps.item) }}
</span>
<i
v-if="slotProps.item.fixtermin"
class="fa-solid fa-lock text-muted"
title="Fixtermin"
></i>
<i
v-if="slotProps.item.abgabedatum"
class="fa-solid fa-file text-muted"
:title="$capitalize($p.t('abgabetool/c4abgabedatum'))"
></i>
<div class="flex-grow-1 text-end">
<span
v-if="slotProps.item.noteBackend?.bezeichnung"
class="fw-bold"
>
{{ slotProps.item.noteBackend?.positiv
? $capitalize($p.t('abgabetool/c4positivBenotet'))
: $capitalize($p.t('abgabetool/c4negativBenotet'))
}}
</span>
<span
v-else-if="slotProps.item.bezeichnung?.benotbar"
class="fw-bold text-muted"
>
{{ $capitalize($p.t('abgabetool/c4notYetGraded')) }}
</span>
</div>
</div>
<div
v-if="slotProps.item.kurzbz"
class="text-muted fst-italic ms-4"
>
{{ slotProps.item.kurzbz }}
</div>
<div
v-if="slotProps.item.abgabedatum"
class="small text-muted ms-4"
>
{{ $capitalize($p.t('abgabetool/c4abgabedatum')) }}:
{{ formatDateFn(slotProps.item.abgabedatum) }}
</div>
<div
v-if="slotProps.item.beurteilungsnotiz"
class="small ms-4 mt-1 text-muted"
>
{{ slotProps.item.beurteilungsnotiz }}
</div>
<div
v-if="slotProps.item.insertamum"
class="small text-muted ms-4 mt-1"
style="font-size: .72rem;"
>
<i class="fa-solid fa-clock-rotate-left me-1"></i>
{{ getSavedTerminInfoString(slotProps.item) }}
</div>
</div>
</template>
</Timeline>
</div>
</div>
<div class="mt-3 border-top pt-2">
<button
class="btn btn-link btn-sm p-0 text-muted text-decoration-none d-flex align-items-center gap-1"
@click="legendExpanded = !legendExpanded"
>
<i :class="legendExpanded ? 'fa-solid fa-chevron-down' : 'fa-solid fa-chevron-right'" style="font-size: 0.7rem;"></i>
Legende
</button>
<div v-show="legendExpanded" class="mt-2">
<AbgabeterminStatusLegende />
</div>
</div>
</div>
`
}
export default AbgabeStudentTimeline;
File diff suppressed because it is too large Load Diff
@@ -5,8 +5,9 @@ import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { getDateStyleClass } from "./getDateStyleClass.js"; import { getDateStyleClass } from "./getDateStyleClass.js";
import { dateFilter } from '../../../tabulator/filters/Dates.js'; import { dateFilter } from '../../../tabulator/filters/DatesManual.js';
import {splitMailsHelper} from "../../../helpers/EmailHelpers.js"; import {splitMailsHelper} from "../../../helpers/EmailHelpers.js";
import { formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js";
export const AbgabetoolMitarbeiter = { export const AbgabetoolMitarbeiter = {
name: "AbgabetoolMitarbeiter", name: "AbgabetoolMitarbeiter",
@@ -31,19 +32,14 @@ export const AbgabetoolMitarbeiter = {
old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link) old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link)
} }
}, },
props: {
viewData: {
type: Object,
required: true,
default: () => ({name: '', uid: ''}),
validator(value) {
return value && value.uid // && value.name -> extensive viewData use only for cis4 onwards
}
}
},
data() { data() {
return { return {
tableData: null, filteredRows: null,
count: 0,
filteredcount: 0,
selectedcount: 0,
qgate1FilterSelected: [],
qgate2FilterSelected: [],
abgabetypenBetreuer: null, abgabetypenBetreuer: null,
detailIsFullscreen: false, detailIsFullscreen: false,
phrasenPromise: null, phrasenPromise: null,
@@ -58,7 +54,7 @@ export const AbgabetoolMitarbeiter = {
allowedNotenOptions: null, allowedNotenOptions: null,
notenOptionsNonFinal: null, notenOptionsNonFinal: null,
serienTermin: Vue.reactive({ serienTermin: Vue.reactive({
datum: new Date(), datum: getViennaTodayISO(),
bezeichnung: { bezeichnung: {
paabgabetyp_kurzbz: 'zwischen', paabgabetyp_kurzbz: 'zwischen',
bezeichnung: 'Zwischenabgabe' bezeichnung: 'Zwischenabgabe'
@@ -80,7 +76,7 @@ export const AbgabetoolMitarbeiter = {
abgabeTableOptions: { abgabeTableOptions: {
minHeight: 250, minHeight: 250,
index: 'projektarbeit_id', index: 'projektarbeit_id',
layout: 'fitDataStretch', layout: 'fitData',
placeholder: Vue.computed(() => this.$p.t('global/noDataAvailable')), placeholder: Vue.computed(() => this.$p.t('global/noDataAvailable')),
selectable: true, selectable: true,
selectableCheck: this.selectionCheck, selectableCheck: this.selectionCheck,
@@ -138,38 +134,82 @@ export const AbgabetoolMitarbeiter = {
handleClick: this.selectAllHandler handleClick: this.selectAllHandler
}, },
width: 50, width: 50,
cssClass: 'sticky-col' cssClass: 'sticky-col',
visible: true
}, },
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, headerFilter: false, headerSort: false, widthGrow: 1, tooltip: false, cssClass: 'sticky-col'}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, headerFilter: false, headerSort: false, width: 140, minWidth: 50, visible: true, tooltip: false, cssClass: 'sticky-col'},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, minWidth: 140, visible: false,tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100,visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100,visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4studstatus'))), field: 'studienstatus', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 150, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4orgformv2'))), field: 'orgform', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, minWidth: 100,visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerartv2'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))), field: 'note_bez', headerFilter: true, visible: false, sorter: this.notenSorter, minWidth: 200, formatter: this.centeredTextFormatter},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))), field: 'prevTermin', {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, visible: true, minWidth: 100},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, width: 500, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerv2'))), field: 'betreuer_full_name', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerTitelPre'))), field: 'betreuer_titelpre', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerVorname'))), field: 'betreuer_vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerNachname'))), field: 'betreuer_nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerTitelPost'))), field: 'betreuer_titelpost', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerv2'))), field: 'zweitbetreuer_full_name', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerTitelPre'))), field: 'zweitbetreuer_titelpre', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerVorname'))), field: 'zweitbetreuer_vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerNachname'))), field: 'zweitbetreuer_nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerTitelPost'))), field: 'zweitbetreuer_titelpost', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerartv2'))), field: 'betreuerart_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))),
headerFilter: dateFilter, headerFilter: dateFilter,
headerFilterFunc: this.headerFilterTerminCol, headerFilterFunc: this.headerFilterTerminCol,
sorter: this.sortFuncTerminCol, sorter: this.sortFuncTerminCol,
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false}, tooltip: this.toolTipFuncPrevTermin,
field: 'prevTermin', formatter: this.abgabeterminFormatter, width: 250, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), field: 'nextTermin', {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), field: 'nextTermin',
headerFilter: dateFilter, headerFilter: dateFilter,
headerFilterFunc: this.headerFilterTerminCol, headerFilterFunc: this.headerFilterTerminCol,
sorter: this.sortFuncTerminCol, sorter: this.sortFuncTerminCol,
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false}, tooltip: this.toolTipFuncNextTermin,
formatter: this.abgabeterminFormatter, width: 250, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))), {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))),
headerFilter: 'list', headerFilter: this.qgateHeaderFilterEditor,
headerFilterParams: { valuesLookup: this.getQGateStatusList }, headerFilterFunc: this.qgateHeaderFilterFunc,
field: 'qgate1Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false}, headerFilterParams: {},
field: 'qgate1Status',
formatter: this.centeredTextFormatter,
titleFormatter: this.shortLongTitleFormatter,
titleFormatterParams: {
shortForm: 'QG1'
},
width: 50,
tooltip: (e, cell) => {
const data = cell.getData();
return data.qgate1Status
}
},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate2Status'))), {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate2Status'))),
headerFilter: 'list', headerFilter: this.qgateHeaderFilterEditor,
headerFilterParams: { valuesLookup: this.getQGateStatusList }, headerFilterFunc: this.qgateHeaderFilterFunc,
field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false} headerFilterParams: {},
field: 'qgate2Status',
formatter: this.centeredTextFormatter,
titleFormatter: this.shortLongTitleFormatter,
titleFormatterParams: {
shortForm: 'QG2'
},
width: 50,
tooltip: (e, cell) => {
const data = cell.getData();
return data.qgate2Status
}
}
], ],
persistence: false, persistence: false,
persistenceID: 'abgabeTableBetreuer2026-02-26' persistenceID: 'abgabeTableBetreuer2026-05-26'
}, },
abgabeTableEventHandlers: [{ abgabeTableEventHandlers: [{
event: "tableBuilt", event: "tableBuilt",
@@ -200,11 +240,388 @@ export const AbgabetoolMitarbeiter = {
}) })
this.selectedData = data this.selectedData = data
this.selectedcount = data.length;
}
},
{
event: 'dataFiltered',
handler: (filters, rows) => {
this.filteredRows = rows;
this.filteredcount = rows.length;
if (!this.selectedData.length) return;
const visibleData = new Set(rows.map(r => r.getData()));
const filteredOut = this.selectedData.filter(sd => !visibleData.has(sd));
if (!filteredOut.length) return;
const filteredOutSet = new Set(filteredOut);
this.$refs.abgabeTable.tabulator.getSelectedRows()
.filter(r => filteredOutSet.has(r.getData()))
.forEach(r => r.deselect());
} }
} }
]}; ]};
}, },
methods: { methods: {
notenSorter(a, b, aRow, bRow, column, dir, sorterParams) {
const aData = aRow.getData()
const bData = bRow.getData()
return aData.note - bData.note
},
async openBenotung(type, link) {
if(type === 'new') {
window.open(link, '_blank')
} else if(type === 'old') {
if(await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/c4aeltereParbeitBenotenv2'),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'btn btn-danger',
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'btn btn-outline-secondary'
}) === false) {
return false
}
window.open(link, '_blank')
} else {
// show info text that no endupload with abgabe has been found
if(await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/c4keinEnduploadErfolgt'),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'btn btn-danger',
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'btn btn-outline-secondary'
}) === false) {
return false
}
}
},
formAction(cell) {
const actionButtons = document.createElement('div');
actionButtons.className = "d-flex gap-3";
actionButtons.style.display = "flex";
actionButtons.style.alignItems = "stretch";
actionButtons.style.justifyContent = "start";
actionButtons.style.height = "100%";
const val = cell.getValue();
const data = cell.getRow().getData()
const createButton = (iconClass, titleKey, clickHandler) => {
const btn = document.createElement('button');
btn.className = 'btn btn-outline-secondary';
btn.style.display = "flex";
btn.style.alignItems = "center"; // center icon vertically
btn.style.justifyContent = "center"; // center icon horizontally
btn.style.height = "100%"; // fill parent container height
btn.style.aspectRatio = "1 / 1"; // keep square shape (optional)
btn.style.padding = "0"; // remove extra padding for compactness
if(iconClass == 'fa fa-timeline') btn.style.transform = "rotate(90deg)";
btn.innerHTML = `<i class="${iconClass}" style="color:#00649C; font-size:1.1rem;"></i>`;
btn.title = this.$capitalize(this.$p.t(titleKey));
btn.addEventListener('click', (e) => {
e.stopPropagation();
e.stopImmediatePropagation();
clickHandler();
});
return btn;
};
actionButtons.append(
createButton('fa fa-folder-open', 'abgabetool/c4details', () => this.setDetailComponent(val)),
);
if(data.isCurrent && data.abgabetermine?.find(termin => termin.paabgabetyp_kurzbz == 'end' && termin.abgabedatum !== null) && data.beurteilungLinkNew) {
actionButtons.append(createButton('fa fa-user-check', 'abgabetool/c4benoten', () => this.openBenotung('new', data.beurteilungLinkNew)))
} else if(data.abgabetermine?.find(termin => termin.paabgabetyp_kurzbz == 'end' && termin.abgabedatum !== null) && data.beurteilungLinkOld) {
actionButtons.append(createButton('fa fa-user-check', 'abgabetool/c4benoten', () => this.openBenotung('old', data.beurteilungLinkOld)))
}
if(this.checkForZweitbetreuerTokenMailAvailability(data)) {
actionButtons.append(createButton('fa fa-envelope-open-text', 'abgabetool/c4zweitBegutachterTokenMailSenden', () => this.sendZweitbetreuerToken(data)))
}
return actionButtons;
},
checkForZweitbetreuerTokenMailAvailability(data) {
const hasEndabgabeWithUpload = !!data.abgabetermine.find(termin => termin.abgabedatum !== null && termin.paabgabetyp_kurzbz == 'end')
const hasZweitbetreuerWithoutBenutzerUid = data.zweitbetreuer_person_id !== null && data.zweitbetreuer_benutzer_uid === null
return hasEndabgabeWithUpload && hasZweitbetreuerWithoutBenutzerUid
},
sendZweitbetreuerToken(data) {
this.$api.call(ApiAbgabe.sendZweitbetreuerTokenMail(data.projektarbeit_id, data.betreuer_person_id, data.student_uid))
.then(res => {
if(res.meta.status == 'success') this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/c4zweitBegutachterTokenMailSuccess'))
})
},
getDateStyleHtml(dateStyle) {
const iconMap = {
'verspaetet': '<i class="fa-solid fa-triangle-exclamation"></i>',
'verpasst': '<i class="fa-solid fa-calendar-xmark"></i>',
'abzugeben': '<i class="fa-solid fa-hourglass-half"></i>',
'standard': '<i class="fa-solid fa-clock"></i>',
'abgegeben': '<i class="fa-solid fa-paperclip"></i>',
'beurteilungerforderlich': '<i class="fa-solid fa-list-check"></i>',
'bestanden': '<i class="fa-solid fa-check"></i>',
'nichtbestanden': '<i class="fa-solid fa-circle-exclamation"></i>',
};
return iconMap[dateStyle] ?? '';
},
statusHeaderFilterEditor(cell, onRendered, success, cancel, editorParams) {
const options = [
{ label: this.$p.t('abgabetool/c4positivBenotet'), value: 'bestanden', dateStyle: 'bestanden' },
{ label: this.$p.t('abgabetool/c4negativBenotet'), value: 'nichtbestanden', dateStyle: 'nichtbestanden' },
{ label: this.$p.t('abgabetool/c4tooltipVerspaetet'), value: 'verspaetet', dateStyle: 'verspaetet' },
{ label: this.$p.t('abgabetool/c4tooltipVerpasst'), value: 'verpasst', dateStyle: 'verpasst' },
{ label: this.$p.t('abgabetool/c4tooltipAbzugeben'), value: 'abzugeben', dateStyle: 'abzugeben' },
{ label: this.$p.t('abgabetool/c4tooltipAbgegeben'), value: 'abgegeben', dateStyle: 'abgegeben' },
{ label: this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich'), value: 'beurteilungerforderlich', dateStyle: 'beurteilungerforderlich' },
{ label: this.$p.t('abgabetool/c4tooltipStandardv2'), value: 'standard', dateStyle: 'standard' },
];
const field = cell.getField();
const stateKey = field + 'FilterSelected'; // e.g. dateStyleFilterSelected
let selected = [...(this[stateKey] || [])];
const wrapper = document.createElement('div');
wrapper.style.cssText = 'position: relative; width: 100%;';
const display = document.createElement('input');
display.readOnly = true;
display.placeholder = '';
display.style.cssText = 'padding: 4px; width: 100%; box-sizing: border-box; cursor: default; border: 1px solid; outline: none; background: #fff; appearance: none; caret-color: transparent;';
const dropdown = document.createElement('div');
dropdown.style.cssText = 'display: none; position: fixed; background: #fff; border: 1px solid; z-index: 9999; min-width: 220px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);';
const updateDisplay = () => {
display.value = options
.filter(o => selected.includes(o.value))
.map(o => o.label)
.join(', ');
};
options.forEach(opt => {
const row = document.createElement('label');
row.style.cssText = 'display: flex; align-items: center; gap: 0; cursor: pointer; white-space: nowrap; padding-right: 8px;';
row.addEventListener('mousedown', e => e.preventDefault());
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.value = opt.value;
cb.checked = selected.includes(opt.value);
cb.style.cssText = 'margin: 0 6px;';
cb.addEventListener('change', () => {
selected = cb.checked
? [...selected, opt.value]
: selected.filter(v => v !== opt.value);
this[stateKey] = [...selected];
updateDisplay();
success([...selected]);
});
// icon badge — same look as cell
const badge = document.createElement('div');
badge.className = opt.dateStyle + '-header';
badge.style.cssText = `min-width: 36px; height: 36px; display: flex; align-items: center;
justify-content: center; flex-shrink: 0; padding: 0px 17px 0px 17px;`;
badge.innerHTML = this.getDateStyleHtml(opt.dateStyle);
const labelText = document.createElement('span');
labelText.textContent = opt.label;
labelText.style.cssText = 'margin-left: 6px;';
row.appendChild(cb);
row.appendChild(badge);
row.appendChild(labelText);
dropdown.appendChild(row);
});
updateDisplay();
display.addEventListener('click', () => {
if (dropdown.style.display === 'none') {
const rect = display.getBoundingClientRect();
dropdown.style.top = rect.bottom + 'px';
dropdown.style.left = rect.left + 'px';
dropdown.style.display = 'block';
} else {
dropdown.style.display = 'none';
}
});
display.addEventListener('blur', () => {
setTimeout(() => { dropdown.style.display = 'none'; }, 150);
});
document.body.appendChild(dropdown);
wrapper.appendChild(display);
cell.getElement().addEventListener('remove', () => dropdown.remove());
onRendered(() => display.focus());
return wrapper;
},
statusHeaderFilterFunc(filterVal, rowVal, rowData, filterParams) {
if (!filterVal || !filterVal.length) return true;
// rowVal is the raw dateStyle string on the flat table
return filterVal.some(val => val === rowVal);
},
qgateHeaderFilterEditor(cell, onRendered, success, cancel, editorParams) {
const options = [
{ label: '[+] ' + this.$p.t('abgabetool/c4positivBenotet'), value: 'positive' },
{ label: '[-] ' + this.$p.t('abgabetool/c4negativBenotet'), value: 'negative' },
{ label: '[~] ' + this.$p.t('abgabetool/c4notYetGraded'), value: 'not_graded' },
{ label: '[?] ' + this.$p.t('abgabetool/c4notSubmitted'), value: 'not_submitted' },
{ label: '[o] ' + this.$p.t('abgabetool/c4notHappenedYet'), value: 'not_happened' },
{ label: '[--] ' + this.$p.t('abgabetool/c4keinTerminVorhanden'), value: 'no_termin' },
];
const field = cell.getField();
const stateKey = field === 'qgate1Status' ? 'qgate1FilterSelected' : 'qgate2FilterSelected';
let selected = [...(this[stateKey] || [])]; // restore persistence state
const wrapper = document.createElement('div');
wrapper.style.cssText = 'position: relative; width: 100%;';
const display = document.createElement('input');
display.readOnly = true;
display.placeholder = '';
display.style.cssText = 'padding: 4px; width: 100%; box-sizing: border-box; cursor: default; border: 1px solid; outline: none; background: #fff; appearance: none; caret-color: transparent;';
const dropdown = document.createElement('div');
dropdown.style.cssText = 'display: none; position: fixed; background: #fff; border: 1px solid; z-index: 9999; min-width: 180px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);';
options.forEach(opt => {
const row = document.createElement('label');
row.style.cssText = 'display: flex; align-items: center; gap: 6px; padding: 4px 8px; cursor: pointer; white-space: nowrap;';
row.addEventListener('mousedown', e => e.preventDefault());
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.value = opt.value;
cb.checked = selected.includes(opt.value); // sync with persistence
cb.addEventListener('change', () => {
if (cb.checked) {
selected.push(opt.value);
} else {
selected = selected.filter(v => v !== opt.value);
}
this[stateKey] = [...selected]; // sync with persistence
display.value = options.filter(o => selected.includes(o.value)).map(o => o.label).join(', ');
success([...selected]);
});
row.appendChild(cb);
row.appendChild(document.createTextNode(opt.label));
dropdown.appendChild(row);
});
display.value = options.filter(o => selected.includes(o.value)).map(o => o.label).join(', ');
display.addEventListener('click', () => {
if (dropdown.style.display === 'none') {
const rect = display.getBoundingClientRect();
dropdown.style.top = rect.bottom + 'px';
dropdown.style.left = rect.left + 'px';
dropdown.style.display = 'block';
} else {
dropdown.style.display = 'none';
}
});
display.addEventListener('blur', () => {
setTimeout(() => { dropdown.style.display = 'none'; }, 150);
});
document.body.appendChild(dropdown);
wrapper.appendChild(display);
cell.getElement().addEventListener('remove', () => dropdown.remove());
onRendered(() => display.focus());
return wrapper;
},
qgateHeaderFilterFunc(filterVal, rowVal, rowData, filterParams) {
if (!filterVal || !filterVal.length) return true;
const matches = (val) => {
switch (val) {
case 'positive': return rowVal === this.$p.t('abgabetool/c4positivBenotet');
case 'negative': return rowVal === this.$p.t('abgabetool/c4negativBenotet');
case 'not_graded': return rowVal === this.$p.t('abgabetool/c4notYetGraded');
case 'not_submitted':return rowVal === this.$p.t('abgabetool/c4notSubmitted');
case 'not_happened': return rowVal === this.$p.t('abgabetool/c4notHappenedYet');
case 'no_termin': return rowVal === this.$p.t('abgabetool/c4keinTerminVorhanden');
default: return true;
}
};
// OR logic — row passes if it matches any selected filter
return filterVal.some(val => matches(val));
},
shortLongTitleFormatter(cell, formatterParams, onRendered) {
const longForm = cell.getValue()
const shortForm = formatterParams?.shortForm
if(longForm && shortForm) {
return `<span class="full-text" style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">
${longForm}
</span>
<span class="short-text" style="font-weight: bold; display: none;">
${shortForm}
</span>`
} else {
return `<span class="full-text" style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">
${longForm}
</span>`
}
},
toolTipFuncPrevTermin(e, cell, onRendered) {
const data = cell.getData();
return this.mapDateStyleToTabulatorTooltip(data.prevTermin.dateStyle);
},
toolTipFuncNextTermin(e, cell, onRendered) {
const data = cell.getData();
return this.mapDateStyleToTabulatorTooltip(data.nextTermin.dateStyle);
},
mapDateStyleToTabulatorTooltip(dateStyleString) {
switch(dateStyleString) {
case 'bestanden':
return this.$p.t('abgabetool/c4tooltipBestanden')
break;
case 'nichtbestanden':
return this.$p.t('abgabetool/c4tooltipNichtBestanden')
break;
case 'beurteilungerforderlich':
return this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich')
break;
case 'verspaetet':
return this.$p.t('abgabetool/c4tooltipVerspaetet')
break;
case 'abgegeben':
return this.$p.t('abgabetool/c4tooltipAbgegeben')
break;
case 'verpasst':
return this.$p.t('abgabetool/c4tooltipVerpasst')
break;
case 'abzugeben':
return this.$p.t('abgabetool/c4tooltipAbzugeben')
break;
case 'standard':
return this.$p.t('abgabetool/c4tooltipStandardv2')
break;
default: return ''
}
},
handlePaUpdated(projektarbeit) { handlePaUpdated(projektarbeit) {
this.checkAbgabetermineProjektarbeit(projektarbeit) this.checkAbgabetermineProjektarbeit(projektarbeit)
this.$refs.abgabeTable.tabulator.redraw(true) this.$refs.abgabeTable.tabulator.redraw(true)
@@ -217,7 +634,7 @@ export const AbgabetoolMitarbeiter = {
}) })
const uniqueRecipients = [...new Set(recipientList)]; const uniqueRecipients = [...new Set(recipientList)];
const subject = ""; // empty subject line const subject = ""; // empty subject line
splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p) splitMailsHelper(uniqueRecipients, param.originalEvent, subject, null, this.$fhcAlert, this.$p)
}, },
getQGateStatusList() { getQGateStatusList() {
return [ return [
@@ -257,7 +674,7 @@ export const AbgabetoolMitarbeiter = {
if (val instanceof Date) { if (val instanceof Date) {
dt = luxon.DateTime.fromJSDate(val); dt = luxon.DateTime.fromJSDate(val);
} else if (typeof val === "string") { } else if (typeof val === "string") {
dt = luxon.DateTime.fromISO(val); dt = toViennaDate(val);
} else { // fallback } else { // fallback
dt = luxon.DateTime.fromMillis(Number(val)); dt = luxon.DateTime.fromMillis(Number(val));
} }
@@ -386,6 +803,9 @@ export const AbgabetoolMitarbeiter = {
} }
this.stateRestored = true this.stateRestored = true
// ensure that the filterCollapseables thingy has the correct values
this.$refs.abgabeTable.setSelectedFields();
} }
}); });
@@ -451,6 +871,32 @@ export const AbgabetoolMitarbeiter = {
projekt.qgate2StatusRank = 1 projekt.qgate2StatusRank = 1
} }
}) })
// set shorthand statuscode once real status has been determined
projekt.qgate1StatusShort = this.mapRankToShortStatus(projekt.qgate1StatusRank)
projekt.qgate2StatusShort = this.mapRankToShortStatus(projekt.qgate2StatusRank)
},
mapRankToShortStatus(rank) {
switch(rank){
case 0: // kein termin vorhanden
return '--'
break;
case 1: // noch nicht stattgefunden
return 'o'
break;
case 2: // noch nicht abgegeben
return '?'
break;
case 3: // noch nicht benotet
return '~'
break;
case 4: // negativ benotet
return '-'
break;
case 5: // positiv benotet
return '+'
break;
}
}, },
checkAbgabetermineProjektarbeit(projekt) { checkAbgabetermineProjektarbeit(projekt) {
const now = luxon.DateTime.now() const now = luxon.DateTime.now()
@@ -460,7 +906,7 @@ export const AbgabetoolMitarbeiter = {
// while already looping through each termin, calculate datestyle beforehand // while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = getDateStyleClass(termin, this.notenOptions) termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
const date = luxon.DateTime.fromISO(termin.datum).endOf('day') const date = toViennaDate(termin.datum).endOf('day')
termin.luxonDate = date termin.luxonDate = date
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
@@ -482,7 +928,7 @@ export const AbgabetoolMitarbeiter = {
// seperate check for quality gates // seperate check for quality gates
this.checkQualityGateStatus(projekt) this.checkQualityGateStatus(projekt)
}, },
abgabterminFormatter(cell) { abgabeterminFormatter(cell) {
const val = cell.getValue() const val = cell.getValue()
if(val) { if(val) {
@@ -514,14 +960,15 @@ export const AbgabetoolMitarbeiter = {
break break
} }
const bezeichnung = val.bezeichnung?.bezeichnung ?? val.bezeichnung const typKurzbz = val.paabgabetyp_kurzbz ?? val.bezeichnung?.paabgabetyp_kurzbz
const bezeichnung = this.$p.t('abgabetool/c4paatyp' + typKurzbz)
return '<div style="display: flex; height: 100%">' + return '<div style="display: flex; height: 100%">' +
'<div class=' + val.dateStyle + "-header" + ' style="width:48px; height: 100%; padding: 0px; display: flex; align-items: center; justify-content: center;">' + '<div class=' + val.dateStyle + "-header" + ' style="min-width:48px; height: 100%; padding: 0px; display: flex; align-items: center; justify-content: center;">' +
icon + icon +
'</div>' + '</div>' +
'<div style="margin-left: 4px;">' + '<div style="margin-left: 4px;">' +
'<p style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+bezeichnung+' - '+ this.formatDate(val.datum)+'</p>' + '<p style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">'+bezeichnung+' - '+ this.formatDate(val.datum)+'</p>' +
'</div>'+ '</div>'+
'</div>' '</div>'
@@ -545,16 +992,19 @@ export const AbgabetoolMitarbeiter = {
}, },
selectAllHandler(e, cell) { selectAllHandler(e, cell) {
const table = cell.getTable(); const table = cell.getTable();
const rows = table.getRows(); const rows = this.filteredRows ?? table.getRows();
// custom select all logic // custom select all logic
const allowed = rows.filter(r => r.getData().selectable); const allowed = rows.filter(r => r.getData().selectable);
// since betreuerpage acctually has logic behind selectable flag, it is important to go over allowed only here
const selected = allowed.every(r => r.isSelected()); const selected = allowed.every(r => r.isSelected());
if(selected) { if(selected){
allowed.forEach(r => r.deselect()); allowed.forEach(r => r.deselect());
e.target.checked = false;
} else { } else {
allowed.forEach(r => r.select()); allowed.forEach(r => r.select());
e.target.checked = true;
} }
// stop built-in handler // stop built-in handler
@@ -565,18 +1015,10 @@ export const AbgabetoolMitarbeiter = {
this.detailIsFullscreen = !this.detailIsFullscreen this.detailIsFullscreen = !this.detailIsFullscreen
}, },
getOptionLabelAbgabetyp(option){ getOptionLabelAbgabetyp(option){
return option.bezeichnung return this.$p.t('abgabetool/c4paatyp' + option.paabgabetyp_kurzbz)
}, },
formatDate(dateParam) { formatDate(dateParam) {
const date = new Date(dateParam) return formatISODate(dateParam);
// handle missing leading 0
const padZero = (num) => String(num).padStart(2, '0');
const month = padZero(date.getMonth() + 1); // Months are zero-based
const day = padZero(date.getDate());
const year = date.getFullYear();
return `${day}.${month}.${year}`;
}, },
undoSelection(cell) { undoSelection(cell) {
// checks if cells row is selected and unselects -> imitates columns which dont trigger row selection // checks if cells row is selected and unselects -> imitates columns which dont trigger row selection
@@ -589,6 +1031,8 @@ export const AbgabetoolMitarbeiter = {
}, },
selectionCheck(row) { selectionCheck(row) {
const data = row.getData() const data = row.getData()
// zweitbetreuer cant select projektarbeiten for serientermine
if(data?.betreuerart_kurzbz == 'Zweitbegutachter') return false if(data?.betreuerart_kurzbz == 'Zweitbegutachter') return false
return true return true
}, },
@@ -612,7 +1056,7 @@ export const AbgabetoolMitarbeiter = {
addSeries() { addSeries() {
this.saving = true this.saving = true
this.$api.call(ApiAbgabe.postSerientermin( this.$api.call(ApiAbgabe.postSerientermin(
this.serienTermin.datum.toISOString(), this.serienTermin.datum,
this.serienTermin.bezeichnung.paabgabetyp_kurzbz, this.serienTermin.bezeichnung.paabgabetyp_kurzbz,
this.serienTermin.bezeichnung.bezeichnung, this.serienTermin.bezeichnung.bezeichnung,
this.serienTermin.kurzbz, this.serienTermin.kurzbz,
@@ -710,7 +1154,7 @@ export const AbgabetoolMitarbeiter = {
termin.allowedToSave = paIsBenotet ? false : true termin.allowedToSave = paIsBenotet ? false : true
// lektoren are not allowed to delete deadlines with existing submissions // lektoren are not allowed to delete deadlines with existing submissions
termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum && !termin.note
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz) termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
@@ -727,28 +1171,37 @@ export const AbgabetoolMitarbeiter = {
}, },
centeredTextFormatter(cell) { centeredTextFormatter(cell) {
const val = cell.getValue() const longForm = cell.getValue()
if(!val) return if(!longForm) return
const data = cell.getData()
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' + const entry = Object.entries(data).find(entry => entry[1] == longForm)
'<p style="max-width: 100%; width: 100%; overflow-wrap: break-word; word-break: break-word; white-space: normal; margin: 0px; text-align: center">'+val+'</p></div>'
// shortFormKey must have same keyname as longForm but with 'Short' appended
const shortForm = data[entry[0]+'Short']
if(shortForm && longForm) {
return `<div style="display: flex; justify-content: start; align-items: center; height: 100%; width: 100%;">
<span class="full-text" style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">
${longForm}
</span>
<span class="short-text" style="font-weight: bold; display: none;">
${shortForm}
</span>
</div>`;
} else {
return '<div style="display: flex; justify-content: start; align-items: center; height: 100%">' +
'<p style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">'+longForm+'</p></div>'
}
}, },
detailFormatter(cell) { detailFormatter(cell) {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' + return '<div style="display: flex; justify-content: start; align-items: center; height: 100%">' +
'<a><i class="fa fa-folder-open" style="color:#00649C"></i></a></div>' '<a><i class="fa fa-folder-open" style="color:#00649C"></i></a></div>'
}, },
beurteilungFormatter(cell) {
const val = cell.getValue()
if(val) {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<a><i class="fa fa-file-pdf" style="color:#00649C"></i></a></div>'
} else return '-'
},
pkzTextFormatter(cell) { pkzTextFormatter(cell) {
const val = cell.getValue() const val = cell.getValue()
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' + return '<div style="display: flex; justify-content: start; align-items: center; height: 100%">' +
'<a style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+val+'</a></div>' '<a style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">'+val+'</a></div>'
}, },
tableResolve(resolve) { tableResolve(resolve) {
this.tableBuiltResolve = resolve this.tableBuiltResolve = resolve
@@ -763,15 +1216,19 @@ export const AbgabetoolMitarbeiter = {
return (projekt.typ + projekt.kurzbz)?.toUpperCase() return (projekt.typ + projekt.kurzbz)?.toUpperCase()
}, },
setupData(data){ setupData(data){
this.projektarbeiten = data[0]
this.domain = data[1] this.domain = data[1]
this.tableData = data[0]?.retval?.map(projekt => { this.projektarbeiten = data[0]?.retval?.map(projekt => {
this.checkAbgabetermineProjektarbeit(projekt) this.checkAbgabetermineProjektarbeit(projekt)
projekt.selectable = projekt.betreuerart_kurzbz !== 'Zweitbegutachter' projekt.selectable = projekt.betreuerart_kurzbz !== 'Zweitbegutachter'
if(this.notenOptions && projekt.note) {
const opt = this.notenOptions.find(n => n.note == projekt.note)
// TODO: mehrsprachig englisch -> nevermind the english field in
// notenoption->bezeichnung_mehrsprachig is ALWAYS german
projekt.note_bez = opt?.bezeichnung
}
return { return {
...projekt, ...projekt,
details: { details: {
@@ -787,9 +1244,10 @@ export const AbgabetoolMitarbeiter = {
titel: projekt.titel titel: projekt.titel
} }
}) })
this.count = this.projektarbeiten.length
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns) this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.$refs.abgabeTable.tabulator.setData(this.tableData); this.$refs.abgabeTable.tabulator.setData(this.projektarbeiten);
}, },
loadProjektarbeiten(all = false, callback) { loadProjektarbeiten(all = false, callback) {
this.$api.call(ApiAbgabe.getMitarbeiterProjektarbeiten(all)) this.$api.call(ApiAbgabe.getMitarbeiterProjektarbeiten(all))
@@ -838,9 +1296,20 @@ export const AbgabetoolMitarbeiter = {
} }
this.serienTermin.upload_allowed = newVal.upload_allowed_default this.serienTermin.upload_allowed = newVal.upload_allowed_default
}, }
}, },
computed: { computed: {
countsToHTML() {
return this.$p.t('global/ausgewaehlt')
+ ': <strong>' + (this.selectedcount || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gefiltert')
+ ': '
+ '<strong>' + (this.filteredcount || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gesamt')
+ ': <strong>' + (this.count || 0) + '</strong>';
},
emailItems() { emailItems() {
const menu = [] const menu = []
@@ -869,6 +1338,8 @@ export const AbgabetoolMitarbeiter = {
} }
}, },
created() { created() {
document.documentElement.classList.add('abgabetool');
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global']) this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true}) this.phrasenPromise.then(()=> {this.phrasenResolved = true})
// fetch config to avoid hard coded links // fetch config to avoid hard coded links
@@ -910,12 +1381,16 @@ export const AbgabetoolMitarbeiter = {
mounted() { mounted() {
this.setupMounted() this.setupMounted()
}, },
beforeUnmount() {
document.documentElement.classList.remove('abgabetool');
},
template: ` template: `
<template v-if="phrasenResolved"> <template v-if="phrasenResolved">
<FhcOverlay :active="loading || saving"></FhcOverlay> <FhcOverlay :active="loading || saving"></FhcOverlay>
<bs-modal ref="modalContainerAddSeries" class="bootstrap-prompt" <bs-modal ref="modalContainerAddSeries" class="bootstrap-prompt"
dialogClass="modal-lg"> dialogClass="modal-lg"
bodyClass="px-4 py-4">
<template v-slot:title> <template v-slot:title>
<div> <div>
{{ $p.t('abgabetool/neueTerminserie') }} {{ $p.t('abgabetool/neueTerminserie') }}
@@ -935,6 +1410,7 @@ export const AbgabetoolMitarbeiter = {
:enable-time-picker="false" :enable-time-picker="false"
locale="de" locale="de"
format="dd.MM.yyyy" format="dd.MM.yyyy"
model-type="yyyy-MM-dd"
:text-input="true" :text-input="true"
auto-apply> auto-apply>
</VueDatePicker> </VueDatePicker>
@@ -982,7 +1458,8 @@ export const AbgabetoolMitarbeiter = {
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt" <bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
dialogClass="modal-xl" :allowFullscreenExpand="true" dialogClass="modal-xl" :allowFullscreenExpand="true"
@toggle-fullscreen="handleToggleFullscreenDetail"> @toggle-fullscreen="handleToggleFullscreenDetail"
bodyClass="px-4 py-4">
<template v-slot:title> <template v-slot:title>
<div> <div>
{{$p.t('abgabetool/c4abgabeMitarbeiterDetailTitle')}} {{$p.t('abgabetool/c4abgabeMitarbeiterDetailTitle')}}
@@ -1001,12 +1478,13 @@ export const AbgabetoolMitarbeiter = {
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways --> <!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div id="abgabetable" style="max-height:40vw;"> <div id="abgabetable" style="max-height:40vw;">
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2> <h2>{{$p.t('abgabetool/abgabetoolTitleBetreuer')}}</h2>
<hr> <hr>
<core-filter-cmpt <core-filter-cmpt
:title="''" :title="''"
@uuidDefined="handleUuidDefined" @uuidDefined="handleUuidDefined"
ref="abgabeTable" ref="abgabeTable"
:description="countsToHTML"
:newBtnShow="true" :newBtnShow="true"
:newBtnLabel="$p.t('abgabetool/neueTerminserie')" :newBtnLabel="$p.t('abgabetool/neueTerminserie')"
:newBtnDisabled="!selectedData.length" :newBtnDisabled="!selectedData.length"
@@ -1,14 +1,17 @@
import AbgabeDetail from "./AbgabeStudentDetail.js"; import AbgabeDetail from "./AbgabeStudentDetail.js";
import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiAbgabe from '../../../api/factory/abgabe.js'
import ApiAuthinfo from '../../../api/factory/authinfo.js';
import BsModal from "../../Bootstrap/Modal.js"; import BsModal from "../../Bootstrap/Modal.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { getDateStyleClass} from "./getDateStyleClass.js"; import { getDateStyleClass} from "./getDateStyleClass.js";
import { validateThesisTitle } from './titleValidation.js'
export const AbgabetoolStudent = { export const AbgabetoolStudent = {
name: "AbgabetoolStudent", name: "AbgabetoolStudent",
components: { components: {
Accordion: primevue.accordion, Accordion: primevue.accordion,
AccordionTab: primevue.accordiontab, AccordionTab: primevue.accordiontab,
Textarea: primevue.textarea,
BsModal, BsModal,
AbgabeDetail, AbgabeDetail,
FhcOverlay FhcOverlay
@@ -17,21 +20,17 @@ export const AbgabetoolStudent = {
return { return {
notenOptions: Vue.computed(() => this.notenOptions), notenOptions: Vue.computed(() => this.notenOptions),
isViewMode: Vue.computed(() => this.isViewMode), isViewMode: Vue.computed(() => this.isViewMode),
moodle_link: Vue.computed(() => this.moodle_link) moodle_link: Vue.computed(() => this.moodle_link),
title_edit_allowed: Vue.computed(() => this.title_edit_allowed),
confetti_on_endupload: Vue.computed(() => this.confetti_on_endupload),
siginfolink_german: Vue.computed(() => this.siginfolink_german),
siginfolink_english: Vue.computed(() => this.siginfolink_english)
} }
}, },
props: { props: {
student_uid_prop: { student_uid_prop: {
default: null default: null
}, },
viewData: {
type: Object,
required: true,
default: () => ({uid: ''}),
validator(value) {
return value && value.uid
}
}
}, },
data() { data() {
return { return {
@@ -44,14 +43,86 @@ export const AbgabetoolStudent = {
detail: null, detail: null,
projektarbeiten: null, projektarbeiten: null,
selectedProjektarbeit: null, selectedProjektarbeit: null,
moodle_link: null moodle_link: null,
uid: null,
title_edit_allowed: null,
confetti_on_endupload: null,
siginfolink_german: null,
siginfolink_english: null,
editingTitel: '',
editingProjektarbeit: null
}; };
}, },
computed: {
isViewMode() {
return this.student_uid !== this.uid
},
student_uid() {
return this.student_uid_prop || this.uid || null
}
},
methods: { methods: {
openTitelEdit(projektarbeit, event) {
// stop the click from toggling the accordion tab
event.stopPropagation();
this.editingProjektarbeit = projektarbeit;
this.editingTitel = projektarbeit.titel ?? '';
this.$refs.modalTitelEdit.show();
},
async saveTitel() {
const validation = validateThesisTitle(this.editingTitel);
if (!validation.isValid) {
if (validation.error === 'empty') {
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4emptyThesisTitle'))
} else if (validation.error === 'invalid_characters') {
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4invalidCharactersThesisTitle'))
}
return false;
}
const confirmed = await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/c4confirmTitelSpeichern'),
acceptLabel: this.$capitalize(this.$p.t('ui/speichern')),
acceptClass: 'p-button-primary',
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'p-button-secondary'
});
if (confirmed === false) return;
this.loading = true;
this.$api.call(
ApiAbgabe.postStudentProjektarbeitTitel(
this.editingProjektarbeit.projektarbeit_id,
validation.cleanedTitle
)
).then(res => {
if (res.meta.status === 'success') {
// update the local list entry in-place so the accordion header reflects it immediately
this.editingProjektarbeit.titel = res.data;
// keep the open detail modal in sync if it happens to be showing this projektarbeit
if (this.selectedProjektarbeit?.projektarbeit_id === this.editingProjektarbeit.projektarbeit_id) {
this.selectedProjektarbeit.titel = res.data;
}
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess')));
this.$refs.modalTitelEdit.hide();
} else {
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4titelSaveError')));
}
}).finally(() => {
this.loading = false;
});
},
handleTitelUpdated(projektarbeit_id, titel) {
const pa = this.projektarbeiten?.find(p => p.projektarbeit_id === projektarbeit_id);
if (pa) pa.titel = titel;
},
checkQualityGatesStrict(termine) { checkQualityGatesStrict(termine) {
let qgate1Passed = false let qgate1Passed = false
let qgate2Passed = false let qgate2Passed = false
termine.forEach(t => { termine.forEach(t => {
const noteOption = this.notenOptions?.find(opt => opt.note == t.note) const noteOption = this.notenOptions?.find(opt => opt.note == t.note)
if(noteOption && noteOption.positiv) { if(noteOption && noteOption.positiv) {
@@ -68,7 +139,7 @@ export const AbgabetoolStudent = {
checkQualityGatesOptional(termine) { checkQualityGatesOptional(termine) {
const qgate1found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate1') const qgate1found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate1')
const qgate2found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate2') const qgate2found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate2')
let qgate1positiv = true let qgate1positiv = true
if(qgate1found) { if(qgate1found) {
qgate1positiv = false qgate1positiv = false
@@ -109,47 +180,35 @@ export const AbgabetoolStudent = {
this.loadAbgaben(details).then((res)=> { this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id) const pa = this.projektarbeiten?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data[0].retval pa.abgabetermine = res.data[0].retval
const paIsBenotet = pa.note !== null const paIsBenotet = pa.note !== null
pa.abgabetermine.forEach(termin => { pa.abgabetermine.forEach(termin => {
termin.file = [] termin.file = []
termin.allowedToUpload = false termin.allowedToUpload = false
if(termin.paabgabetyp_kurzbz == 'end') { if(termin.paabgabetyp_kurzbz == 'end') {
// old assumed production logic when qgates are required
// termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGatesStrict(pa.abgabetermine)
const inTime = termin.fixtermin ? !this.isPastDate(termin.datum) : true const inTime = termin.fixtermin ? !this.isPastDate(termin.datum) : true
termin.allowedToUpload = inTime && this.checkQualityGatesOptional(pa.abgabetermine) termin.allowedToUpload = inTime && this.checkQualityGatesOptional(pa.abgabetermine)
// development purposes
// termin.allowedToUpload = this.checkQualityGatesStrict(pa.abgabetermine)
// termin.allowedToUpload = true
} else if(termin.fixtermin) { } else if(termin.fixtermin) {
termin.allowedToUpload = !this.isPastDate(termin.datum) termin.allowedToUpload = !this.isPastDate(termin.datum)
} else { } else {
// this could confuse people since we should dont show people this flag termin.allowedToUpload = termin.upload_allowed
termin.allowedToUpload = termin.upload_allowed
} }
// blocks client upload button if projektarbeitet is already beurteilt und thus further abgaben on any termin should be blocked
if(paIsBenotet) termin.allowedToUpload = false if(paIsBenotet) termin.allowedToUpload = false
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz) termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
termin.dateStyle = getDateStyleClass(termin, this.notenOptions) termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
}) })
pa.betreuer = this.buildBetreuer(pa) pa.betreuer = this.buildBetreuer(pa)
pa.student_uid = this.student_uid pa.student_uid = this.student_uid
this.selectedProjektarbeit = pa this.selectedProjektarbeit = pa
this.$refs.modalContainerAbgabeDetail.show() this.$refs.modalContainerAbgabeDetail.show()
}).finally(()=>{this.loading=false}) }).finally(()=>{this.loading=false})
}, },
centeredTextFormatter(cell) { centeredTextFormatter(cell) {
@@ -171,8 +230,8 @@ export const AbgabetoolStudent = {
}, },
mailFormatter(cell) { mailFormatter(cell) {
const val = cell.getValue() const val = cell.getValue()
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%;">' + return '<div style="display: flex; justify-content: center; align-items: center; height: 100%;">' +
'<a href='+val+'><i class="fa fa-envelope" style="color:#00649C"></i></a></div>' '<a href='+val+'><i class="fa fa-envelope" style="color:#00649C"></i></a></div>'
}, },
beurteilungFormatter(cell) { beurteilungFormatter(cell) {
const val = cell.getValue() const val = cell.getValue()
@@ -182,19 +241,17 @@ export const AbgabetoolStudent = {
} else return '-' } else return '-'
}, },
buildMailToLink(projekt) { buildMailToLink(projekt) {
// should always be "projekt.mitarbeiter_uid +'@'+ this.domain", built in backend
return 'mailto:' + projekt.email return 'mailto:' + projekt.email
}, },
buildBetreuer(abgabe) { buildBetreuer(abgabe) {
return (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '') return (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
}, },
async setupData(data){ async setupData(data){
// this.projektarbeiten = data[0]
const projektarbeiten = data[0] ?? null const projektarbeiten = data[0] ?? null
if(!projektarbeiten) return if(!projektarbeiten) return
this.projektarbeiten = projektarbeiten.map(projekt => { this.projektarbeiten = projektarbeiten.map(projekt => {
let mode = 'detailTermine' let mode = 'detailTermine'
return { return {
...projekt, ...projekt,
details: { details: {
@@ -228,16 +285,14 @@ export const AbgabetoolStudent = {
.then(res => { .then(res => {
resolve(res) resolve(res)
}) })
}) })
}, },
async setupMounted() { async setupMounted() {
this.loadProjektarbeiten() this.loadProjektarbeiten()
}, },
getAccTabHeaderForProjektarbeit(projektarbeit) { getAccTabHeaderForProjektarbeit(projektarbeit) {
let title = '' let title = ''
title += projektarbeit.titel ?? this.$p.t('abgabetool/keinTitel') title += projektarbeit.titel ?? this.$p.t('abgabetool/keinTitel')
return title return title
}, },
getMailLink(projektarbeit) { getMailLink(projektarbeit) {
@@ -245,6 +300,31 @@ export const AbgabetoolStudent = {
return 'mailto:'+projektarbeit.email return 'mailto:'+projektarbeit.email
} else return '' } else return ''
}, },
getZweitbetreuerMailLink(projektarbeit) {
if(projektarbeit.zweitbetreuer_mail) {
return 'mailto:'+projektarbeit.zweitbetreuer_mail
} else return ''
},
hasZweitbetreuer(projektarbeit) {
return !!(projektarbeit.zweitbetreuer_person_id || projektarbeit.zweitbetreuer)
},
getErstbetreuerEmailLabel(projektarbeit) {
// with only one betreuer keep the generic label; with multiple betreuer
// distinguish the email rows by their betreuungsart bezeichnung
if(!this.hasZweitbetreuer(projektarbeit)) {
return this.$capitalize(this.$p.t('abgabetool/c4betreuerEmailKontaktv2'))
}
const art = projektarbeit.betreuerart_kurzbz
? this.$p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz)
: this.$p.t('abgabetool/c4betreuerv2')
return this.$capitalize(this.$p.t('abgabetool/c4emailBetreuungsart', [art]))
},
getZweitbetreuerEmailLabel(projektarbeit) {
const art = projektarbeit.zweitbetreuer_betreuerart_kurzbz
? this.$p.t('abgabetool/c4betrart' + projektarbeit.zweitbetreuer_betreuerart_kurzbz)
: ''
return this.$capitalize(this.$p.t('abgabetool/c4emailBetreuungsart', [art]))
},
getNoteBezeichnung(projektarbeit) { getNoteBezeichnung(projektarbeit) {
if(projektarbeit.note && this.notenOptions) { if(projektarbeit.note && this.notenOptions) {
const noteOpt = this.notenOptions.find(opt => opt.note == projektarbeit.note) const noteOpt = this.notenOptions.find(opt => opt.note == projektarbeit.note)
@@ -258,25 +338,21 @@ export const AbgabetoolStudent = {
}, },
handleDownloadBeurteilung2(projektarbeit) { handleDownloadBeurteilung2(projektarbeit) {
window.open(projektarbeit.beurteilung2) window.open(projektarbeit.beurteilung2)
}
},
watch: {
},
computed: {
isViewMode() {
return this.student_uid !== this.viewData.uid
}, },
student_uid() { async fetchAuthUID() {
return this.student_uid_prop || this.viewData?.uid || null const authIdResponse = await this.$api.call(ApiAuthinfo.getAuthUID());
} this.uid = authIdResponse.data.uid;
},
}, },
watch: {},
async created() { async created() {
// make sure zoom media query doesnt spill ever to other CIS4 sites
document.documentElement.classList.add('abgabetool');
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global']) this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true}) this.phrasenPromise.then(()=> {this.phrasenResolved = true})
this.loading = true this.loading = true
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
await this.$api.call(ApiAbgabe.getNoten()).then(res => { await this.$api.call(ApiAbgabe.getNoten()).then(res => {
if(res.meta.status == 'success') { if(res.meta.status == 'success') {
this.notenOptions = res.data[0] this.notenOptions = res.data[0]
@@ -289,43 +365,94 @@ export const AbgabetoolStudent = {
this.loading = false this.loading = false
}) })
// fetch abgabetypen options
this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => { this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => {
this.abgabeTypeOptions = res.data this.abgabeTypeOptions = res.data
}).catch(e => { }).catch(e => {
this.loading = false this.loading = false
}) })
// fetch config to avoid hard coded links
this.$api.call(ApiAbgabe.getConfigStudent()).then(res => { this.$api.call(ApiAbgabe.getConfigStudent()).then(res => {
this.moodle_link = res.data?.moodle_link this.moodle_link = res.data?.moodle_link
this.title_edit_allowed = res.data?.title_edit_allowed
this.confetti_on_endupload = res.data?.confetti_on_endupload
this.siginfolink_german = res.data?.siginfolink_german
this.siginfolink_english = res.data?.siginfolink_english
}).catch(e => { }).catch(e => {
this.loading = false this.loading = false
}) })
await this.fetchAuthUID();
}, },
mounted() { mounted() {
this.setupMounted() this.setupMounted()
}, },
beforeUnmount() {
document.documentElement.classList.remove('abgabetool');
},
template: ` template: `
<template v-if="phrasenResolved"> <template v-if="phrasenResolved">
<FhcOverlay :active="loading"></FhcOverlay> <FhcOverlay :active="loading"></FhcOverlay>
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt" <bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
dialogClass="modal-xl" :allowFullscreenExpand="true"> dialogClass="modal-xl" :allowFullscreenExpand="true" bodyClass="px-4 py-4">
<template v-slot:title> <template v-slot:title>
<div> <div>
{{$capitalize( $p.t('abgabetool/c4abgabeStudentDetailTitle') )}} {{$capitalize( $p.t('abgabetool/c4abgabeStudentDetailTitle') )}}
</div> </div>
</template> </template>
<template v-slot:default> <template v-slot:default>
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail> <AbgabeDetail
:projektarbeit="selectedProjektarbeit"
@titel-updated="handleTitelUpdated"
></AbgabeDetail>
</template>
</bs-modal>
<bs-modal
ref="modalTitelEdit"
class="bootstrap-prompt"
dialogClass="bordered-modal"
bodyClass="px-4 py-4"
>
<template v-slot:title>
{{$capitalize( $p.t('abgabetool/c4titelBearbeiten') )}}
</template>
<template v-slot:default>
<div class="mb-2">
<label class="form-label fw-bold">
{{$capitalize( $p.t('abgabetool/c4titel') )}}
</label>
<Textarea
v-model="editingTitel"
rows="2"
maxlength="1024"
class="form-control w-100"
@keydown.enter.prevent="saveTitel"
/>
<div class="form-text text-end">{{ editingTitel.length }} / 1024</div>
</div>
</template>
<template v-slot:footer>
<button
class="btn btn-secondary"
@click="$refs.modalTitelEdit.hide()"
>
{{$capitalize( $p.t('abgabetool/c4Cancel') )}}
</button>
<button
class="btn btn-primary"
:disabled="!editingTitel.trim()"
@click="saveTitel"
>
<i class="fa-solid fa-floppy-disk me-1"></i>
{{$capitalize( $p.t('ui/speichern') )}}
</button>
</template> </template>
</bs-modal> </bs-modal>
<h2>{{$capitalize( $p.t('abgabetool/abgabetoolTitle') )}}</h2> <h2>{{$capitalize( $p.t('abgabetool/abgabetoolTitle') )}}</h2>
<hr> <hr>
<div v-if="projektarbeiten === null"> <div v-if="projektarbeiten === null || projektarbeiten?.length == 0">
{{$capitalize( $p.t('abgabetool/c4abgabeStudentNoProjectsFound') )}} {{$capitalize( $p.t('abgabetool/c4abgabeStudentNoProjectsFound') )}}
</div> </div>
@@ -335,8 +462,12 @@ export const AbgabetoolStudent = {
<template #header> <template #header>
<div class="d-flex row w-100"> <div class="d-flex row w-100">
<div class="text-start" :class="projektarbeit.note != null ? 'col-6' : 'col-12'"> <div class="text-start" :class="projektarbeit.note != null ? 'col-6' : 'col-12'"
<span>{{getAccTabHeaderForProjektarbeit(projektarbeit)}}</span> style="min-width: 0;">
<span
:title="getAccTabHeaderForProjektarbeit(projektarbeit)"
style="display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 600px;"
>{{getAccTabHeaderForProjektarbeit(projektarbeit)}}</span>
</div> </div>
<div class="col-6 text-end"> <div class="col-6 text-end">
<span>{{getNoteBezeichnung(projektarbeit)}}</span> <span>{{getNoteBezeichnung(projektarbeit)}}</span>
@@ -385,7 +516,7 @@ export const AbgabetoolStudent = {
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4betreuerEmailKontaktv2') )}}</div> <div class="col-4 col-md-3 fw-bold">{{ getErstbetreuerEmailLabel(projektarbeit) }}</div>
<div class="col-8 col-md-9"> <div class="col-8 col-md-9">
<a :href="getMailLink(projektarbeit)"><i class="fa fa-envelope" style="color:#00649C"></i></a> <a :href="getMailLink(projektarbeit)"><i class="fa fa-envelope" style="color:#00649C"></i></a>
</div> </div>
@@ -396,18 +527,46 @@ export const AbgabetoolStudent = {
{{ projektarbeit.zweitbetreuer?.first }} {{ projektarbeit.zweitbetreuer?.first }}
</div> </div>
</div> </div>
<div v-if="projektarbeit.zweitbetreuer_person_id || projektarbeit.zweitbetreuer" class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{ getZweitbetreuerEmailLabel(projektarbeit) }}</div>
<div class="col-8 col-md-9">
<a v-if="projektarbeit.zweitbetreuer_mail" :href="getZweitbetreuerMailLink(projektarbeit)"><i class="fa fa-envelope" style="color:#00649C"></i></a>
<span v-else>-</span>
</div>
</div>
<div class="row mt-2"> <div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4projekttyp') )}}</div> <div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4projekttyp') )}}</div>
<div class="col-8 col-md-9"> <div class="col-8 col-md-9">
{{ projektarbeit.projekttypbezeichnung }} {{ projektarbeit.projekttypbezeichnung }}
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4titel') )}}</div> <div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4titel') )}}</div>
<div class="col-8 col-md-9"> <div class="col-8 col-md-9 d-flex align-items-center gap-2" style="min-width: 0;">
{{ projektarbeit.titel }} <span
:title="projektarbeit.titel"
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
>{{ projektarbeit.titel }}</span>
<button
v-if="title_edit_allowed && !isViewMode && projektarbeit.note == null"
class="btn btn-sm btn-outline-secondary border-0 p-1"
v-tooltip.right="{ value: $capitalize($p.t('abgabetool/c4titelBearbeiten')), class: 'custom-tooltip' }"
@click="openTitelEdit(projektarbeit, $event)"
>
<i class="fa-solid fa-pen"></i>
</button>
</div> </div>
</div> </div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4note') )}}</div>
<div class="col-8 col-md-9">
<span>{{getNoteBezeichnung(projektarbeit)}}</span>
</div>
</div>
</AccordionTab> </AccordionTab>
</template> </template>
</Accordion> </Accordion>
@@ -10,14 +10,6 @@ export const DeadlineOverview = {
person_uid_prop: { person_uid_prop: {
default: null default: null
}, },
viewData: {
type: Object,
required: true,
default: () => ({name: '', uid: ''}),
validator(value) {
return value && value.name && value.uid
}
}
}, },
data() { data() {
return { return {
@@ -0,0 +1,33 @@
const zone = 'Europe/Vienna';
export function getViennaTodayISO() {
return luxon.DateTime.now().setZone(zone).toISODate();
}
export function formatISODate(dateParam) {
if (!dateParam) return '';
const date = luxon.DateTime.fromISO(String(dateParam), { zone });
return date.isValid ? date.toFormat('dd.MM.yyyy') : '';
}
export function formatDateTime(dateParam) {
if (!dateParam) return '';
const date = luxon.DateTime.fromSQL(dateParam);
return date.isValid ? date.toFormat("dd.MM.yyyy HH:mm") : '';
}
export function toViennaDate(dateParam) {
if (!dateParam) return null;
return luxon.DateTime.fromISO(String(dateParam), { zone });
}
export function compareISODateValues(a, b) {
if (!a && !b) return 0;
if (!a) return 1;
if (!b) return -1;
return String(a).localeCompare(String(b));
}
@@ -1,8 +1,8 @@
const zone = 'Europe/Vienna'; const zone = 'Europe/Vienna';
const today = luxon.DateTime.now().setZone(zone);
export function getDateStyleClass(termin, notenOptions) { export function getDateStyleClass(termin, notenOptions) {
const today = luxon.DateTime.now().setZone(zone);
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day'); const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null; const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
termin.diffindays = datum.diff(today, 'days').days; termin.diffindays = datum.diff(today, 'days').days;
@@ -28,10 +28,11 @@ export function getDateStyleClass(termin, notenOptions) {
// no submission yet // no submission yet
if (datum < today) return 'verpasst'; if (datum < today) return 'verpasst';
if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
} }
// GENERIC STATUS // GENERIC STATUS — applies to all termine
return datum < today ? 'verpasst' : 'standard'; if (datum < today) return 'verpasst';
} if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
}
@@ -0,0 +1,25 @@
/**
* Validates the thesis title on the frontend
* @param {string} title - The raw input from the title field
* @returns {object} Validation result containing status and cleaned title
*/
export function validateThesisTitle(title) {
if (!title) {
return { isValid: false, error: 'empty' };
}
// Replicate strip_tags / trim
const cleanedTitle = title.replace(/<\/?[^>]+(>|$)/g, "").trim();
if (cleanedTitle === '') {
return { isValid: false, error: 'empty' };
}
// Replicate the emoji/pictograph rejection
const emojiRegex = /\p{Extended_Pictographic}/u;
if (emojiRegex.test(cleanedTitle)) {
return { isValid: false, error: 'invalid_characters' };
}
return { isValid: true, cleanedTitle: cleanedTitle };
}
+114 -92
View File
@@ -2,115 +2,137 @@ import Pagination from "../../Pagination/Pagination.js";
import StudiengangInformation from "./StudiengangInformation/StudiengangInformation.js"; import StudiengangInformation from "./StudiengangInformation/StudiengangInformation.js";
import BsConfirm from "../../Bootstrap/Confirm.js"; import BsConfirm from "../../Bootstrap/Confirm.js";
import ApiCms from '../../../api/factory/cms.js'; import ApiCms from "../../../api/factory/cms.js";
export default { export default {
name: "NewsComponent", name: "NewsComponent",
components: { components: {
Pagination, Pagination,
StudiengangInformation, StudiengangInformation,
},
data() {
return {
content: null,
maxPageCount: 0,
page_size: 10,
page:1,
};
},
watch:{
'$p.user_language.value':function(sprache){
this.fetchNews();
}
},
computed:{
sprache: function(){
return this.$p.user_language.value;
}, },
}, inject: ["isMobile"],
methods: { data() {
fetchNews() { return {
return this.$api content: null,
.call(ApiCms.getNews(this.page, this.page_size, this.sprache)) maxPageCount: 0,
.then(res => res.data) page_size: 10,
.then(result => { page: 1,
this.content = result; };
},
watch: {
"$p.user_language.value": function (sprache) {
this.fetchNews();
},
},
computed: {
sprache: function () {
return this.$p.user_language.value;
},
},
methods: {
async fetchNews() {
let newsResponse = await this.$api.call(
ApiCms.getNews(this.page, this.page_size, this.sprache),
);
this.content = newsResponse.data;
document.querySelectorAll("#cms [data-confirm]").forEach((el) => { document.querySelectorAll("#cms [data-confirm]").forEach((el) => {
el.addEventListener("click", (evt) => { el.addEventListener("click", (evt) => {
evt.preventDefault(); evt.preventDefault();
BsConfirm.popup(el.dataset.confirm) BsConfirm.popup(el.dataset.confirm)
.then(() => { .then(() => {
Axios.get(el.href) Axios.get(el.href)
.then((res) => { .then((res) => {
// TODO(chris): check for success then show message and/or reload // TODO(chris): check for success then show message and/or reload
location = location; location = location;
})
.catch((err) => console.error("ERROR:", err));
}) })
.catch(() => { .catch((err) => console.error("ERROR:", err));
}); })
}); .catch(() => {});
}); });
document.querySelectorAll("#cms [data-href]").forEach((el) => { });
el.href = el.dataset.href.replace( document.querySelectorAll("#cms [data-href]").forEach((el) => {
/^ROOT\//, el.href = el.dataset.href.replace(
FHC_JS_DATA_STORAGE_OBJECT.app_root /^ROOT\//,
); FHC_JS_DATA_STORAGE_OBJECT.app_root,
}); );
Vue.nextTick(()=>{ });
document.querySelectorAll(".card-header").forEach((el) => {
el.classList.add("fhc-primary");
});
document.querySelectorAll(".row").forEach((el) => {
el.classList.add("w-100");
el.classList.add("align-items-center");
});
document.querySelectorAll(".row h2").forEach((el) => {
el.classList.add("mb-0");
});
}) await this.$nextTick();
this.formatExternalHtml();
},
async loadNewPageContent(data) {
let newsResponse = await this.$api.call(
ApiCms.getNews(data.page, data.rows),
);
this.content = newsResponse.data;
await this.$nextTick();
this.formatExternalHtml();
},
formatExternalHtml() {
document
.querySelectorAll(".news-list-item .card-header")
.forEach((el) => {
el.classList.add("fhc-primary");
});
document.querySelectorAll(".news-list-item .row").forEach((el) => {
el.classList.add("w-100");
el.classList.add("align-items-center");
});
document
.querySelectorAll(".news-list-item .row h2")
.forEach((el) => {
el.classList.add("mb-0");
}); });
}, },
loadNewPageContent(data) { afterPageUpdated(event) {
this.$api this.page = event.page;
.call(ApiCms.getNews(data.page, data.rows)) this.page_size = event.rows;
.then(res => res.data) this.$refs.newsPageHeading.scrollIntoView({block: 'end'});
.then(result => { this.loadNewPageContent(event);
this.content = result; },
},
}); created() {
} this.fetchNews();
},
created() {
this.fetchNews();
this.$api this.$api
.call(ApiCms.getNewsRowCount()) .call(ApiCms.getNewsRowCount())
.then(res => res.data) .then((res) => res.data)
.then(result => { .then((result) => {
this.maxPageCount = result; this.maxPageCount = result;
}); });
}, },
template: /*html*/ ` template: /*html*/ `
<h2 class="fhc-primary-color">News</h2> <div :class="{'pb-3': isMobile}" class="overflow-x-hidden">
<hr/> <h2 ref="newsPageHeading" class="fhc-primary-color">News</h2>
<pagination v-show="content?true:false" :page_size="page_size" @page="page=$event.page; loadNewPageContent($event)" :maxPageCount="maxPageCount"> <hr/>
</pagination> <pagination
<div class="container-fluid mt-4"> v-if="content?true:false"
<div class="row"> :page="page"
<div class="col" v-html="content"> :page_size="page_size"
</div> @pageUpdated="afterPageUpdated($event)"
<div class="col-auto"> :maxPageCount="maxPageCount"
<div style="width:15rem"> ></pagination>
<studiengang-information></studiengang-information> <div class="container-fluid mt-4">
<div class="row">
<div class="col" v-html="content">
</div>
<div class="col-auto">
<div style="width:15rem">
<studiengang-information></studiengang-information>
</div>
</div> </div>
</div> </div>
</div> </div>
<pagination
v-if="content?true:false"
:page="page"
:page_size="page_size"
@pageUpdated="afterPageUpdated($event)"
:maxPageCount="maxPageCount"
></pagination>
</div> </div>
<pagination v-show="content?true:false" :page_size="page_size" @page="loadNewPageContent" :maxPageCount="maxPageCount">
</pagination>
`, `,
}; };
@@ -29,51 +29,51 @@ components:{
}, },
template:/*html*/` template:/*html*/`
<div id="fhc-studiengang-informationen"> <div id="fhc-studiengang-informationen">
<template v-if="studiengang?.bezeichnung && semester"> <template v-if="studiengang?.bezeichnung && semester">
<div class="card card-body mb-3 border-0"> <div class="card card-body mb-3 border-0">
<div class="mb-1"> <div class="mb-1">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiengang')}}:</h2> <h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiengang')}}:</h2>
<span class="mb-1">{{studiengang?.bezeichnung}}</span> <span class="mb-1">{{studiengang?.bezeichnung}}</span>
</div>
<div class="mb-1">
<h2 class="h4 mb-1 pb-0">Moodle:</h2>
<a class="fhc-link-color mb-1" target="_blank" :href="moodleLink">{{studiengang?.kurzbzlang}}</a>
</div>
<div :class="{'mb-1':studiengang?.zusatzinfo_html}">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiensemester')}}: </h2>
<span class="mb-1">{{semester}}</span>
</div>
<div class="zusatzinfo" v-if="studiengang?.zusatzinfo_html" v-html="studiengang?.zusatzinfo_html"></div>
</div>
</template>
<template v-for="{title, collection} in collection_array">
<template v-if="Array.isArray(collection) && collection.length !==0">
<h2 class="h5 text-truncate">{{title}}</h2>
<template v-if="displayWidget">
<div class="d-flex flex-wrap flex-row mb-3 gap-2">
<template v-for="person in collection">
<studiengang-person displayWidget v-bind="person"></studiengang-person>
</template>
</div> </div>
</template> <div class="mb-1">
<template v-else> <h2 class="h4 mb-1 pb-0">Moodle:</h2>
<template v-for="person in collection"> <a class="fhc-link-color mb-1" target="_blank" :href="moodleLink">{{studiengang?.kurzbzlang}}</a>
<div class="mb-3"> </div>
<studiengang-person v-bind="person"></studiengang-person> <div :class="{'mb-1':studiengang?.zusatzinfo_html}">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiensemester')}}: </h2>
<span class="mb-1">{{semester}}</span>
</div>
<div class="zusatzinfo" v-if="studiengang?.zusatzinfo_html" v-html="studiengang?.zusatzinfo_html"></div>
</div>
</template>
<template v-for="{title, collection} in collection_array">
<template v-if="Array.isArray(collection) && collection.length !==0">
<h2 class="h5 text-truncate">{{title}}</h2>
<template v-if="displayWidget">
<div class="d-flex flex-wrap flex-row mb-3 gap-2">
<template v-for="person in collection">
<studiengang-person displayWidget v-bind="person"></studiengang-person>
</template>
</div> </div>
</template> </template>
<template v-else>
<template v-for="person in collection">
<div class="mb-3">
<studiengang-person v-bind="person"></studiengang-person>
</div>
</template>
</template>
</template> </template>
</template> </template>
</template> <template v-if="hochschulvertr && Array.isArray(hochschulvertr) && hochschulvertr.length >0">
<template v-if="hochschulvertr && Array.isArray(hochschulvertr) && hochschulvertr.length >0"> <studiengang-vertretung showBezeichnung :title="$p.t('studiengangInformation', 'Hochschulvertretung')" :vertretungsList="hochschulvertr"></studiengang-vertretung>
<studiengang-vertretung showBezeichnung :title="$p.t('studiengangInformation', 'Hochschulvertretung')" :vertretungsList="hochschulvertr"></studiengang-vertretung> </template>
</template> <template v-if="stdv && Array.isArray(stdv) && stdv.length >0">
<template v-if="stdv && Array.isArray(stdv) && stdv.length >0"> <studiengang-vertretung :title="$p.t('studiengangInformation', 'Studienvertretung').concat(studiengang?.kurzbzlang??'')" :vertretungsList="stdv"></studiengang-vertretung>
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Studienvertretung').concat(studiengang.kurzbzlang??'')" :vertretungsList="stdv"></studiengang-vertretung> </template>
</template> <template v-if="jahrgangsvertr && Array.isArray(jahrgangsvertr) && jahrgangsvertr.length >0">
<template v-if="jahrgangsvertr && Array.isArray(jahrgangsvertr) && jahrgangsvertr.length >0"> <studiengang-vertretung :title="$p.t('studiengangInformation', 'Jahrgangsvertretung')" :vertretungsList="jahrgangsvertr"></studiengang-vertretung>
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Jahrgangsvertretung')" :vertretungsList="jahrgangsvertr"></studiengang-vertretung> </template>
</template>
</div> </div>
`, `,
@@ -27,7 +27,7 @@ export default {
<dl class="stgkontaktinfo"> <dl class="stgkontaktinfo">
<dt><i class="fa fa-phone me-2"></i></dt> <dt><i class="fa fa-phone me-2"></i></dt>
<dd class="mb-3"><a class="fhc-link-color" :href="phone.link">{{phone.number}}</a></dd> <dd class="mb-3"><a class="fhc-link-color" :href="phone?.link">{{phone?.number}}</a></dd>
<dt><i class="fa fa-home me-2"></i></dt> <dt><i class="fa fa-home me-2"></i></dt>
<dd class="mb-3">{{ort}}</dd> <dd class="mb-3">{{ort}}</dd>
@@ -3,7 +3,8 @@ import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js'; import ApiLvPlan from '../../../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js'; import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week' export const DEFAULT_MODE_LVPLAN_MOBILE = 'List';
export const DEFAULT_MODE_LVPLAN_DESKTOP = 'Week';
export default { export default {
name: 'LvPlanLehrveranstaltung', name: 'LvPlanLehrveranstaltung',
@@ -19,15 +20,21 @@ export default {
lv: null lv: null
}; };
}, },
inject: ["isMobile"],
computed:{ computed:{
currentDay() { currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date))) if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate(); return luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate();
return this.propsViewData?.focus_date; return this.propsViewData?.focus_date;
}, },
currentMode() { currentMode() {
if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase())) let validModes = ['day', 'month'];
return DEFAULT_MODE_LVPLAN; validModes.push(this.isMobile ? 'list' : 'week');
const defaultMode = this.isMobile ? DEFAULT_MODE_LVPLAN_MOBILE : DEFAULT_MODE_LVPLAN_DESKTOP;
if (!this.propsViewData?.mode || !validModes.includes(this.propsViewData?.mode.toLowerCase()))
return defaultMode;
return this.propsViewData?.mode; return this.propsViewData?.mode;
}, },
currentLv() { currentLv() {
@@ -47,6 +54,17 @@ export default {
return this.lv.bezeichnung; return this.lv.bezeichnung;
} }
}, },
watch: {
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: { methods: {
handleChangeDate(day, newMode) { handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day); return this.handleChangeMode(newMode, day);
@@ -95,7 +113,6 @@ export default {
<fhc-calendar <fhc-calendar
v-else-if="lv" v-else-if="lv"
ref="calendar" ref="calendar"
:timezone="viewData.timezone"
:get-promise-func="getPromiseFunc" :get-promise-func="getPromiseFunc"
:date="currentDay" :date="currentDay"
:mode="currentMode" :mode="currentMode"
+10 -8
View File
@@ -3,7 +3,8 @@ import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js'; import ApiLvPlan from '../../../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js'; import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week' export const DEFAULT_MODE_LVPLAN_MOBILE = 'List';
export const DEFAULT_MODE_LVPLAN_DESKTOP = 'Week';
export default { export default {
name: 'LvPlan', name: 'LvPlan',
@@ -11,31 +12,32 @@ export default {
FhcCalendar FhcCalendar
}, },
props: { props: {
viewData: Object, // NOTE(chris): this is inherited from router-view
propsViewData: Object propsViewData: Object
}, },
data() { data() {
const now = luxon.DateTime.now().setZone(this.viewData.timezone);
return { return {
studiensemester_kurzbz: null, studiensemester_kurzbz: null,
studiensemester_start: null, studiensemester_start: null,
studiensemester_ende: null, studiensemester_ende: null,
uid: null, uid: null,
lv: null lv: null,
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
}; };
}, },
inject: ["isMobile"],
computed:{ computed:{
currentDay() { currentDay() {
return this.propsViewData?.focus_date || luxon.DateTime.now().setZone(this.viewData.timezone).toISODate(); return this.propsViewData?.focus_date || luxon.DateTime.now().setZone(this.timezone).toISODate();
}, },
currentMode() { currentMode() {
return this.propsViewData?.mode || DEFAULT_MODE_LVPLAN; const defaultMode = this.isMobile ? DEFAULT_MODE_LVPLAN_MOBILE : DEFAULT_MODE_LVPLAN_DESKTOP;
return this.propsViewData?.mode || defaultMode;
}, },
downloadLinks() { downloadLinks() {
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid) if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
return false; return false;
const opts = { zone: this.viewData.timezone }; const opts = { zone: this.timezone };
const start = luxon.DateTime const start = luxon.DateTime
.fromISO(this.studiensemester_start, opts) .fromISO(this.studiensemester_start, opts)
.toUnixInteger(); .toUnixInteger();
@@ -115,7 +117,7 @@ export default {
<fhc-calendar <fhc-calendar
ref="calendar" ref="calendar"
v-model:lv="lv" v-model:lv="lv"
:timezone="viewData.timezone" :timezone="timezone"
:get-promise-func="getPromiseFunc" :get-promise-func="getPromiseFunc"
:date="currentDay" :date="currentDay"
:mode="currentMode" :mode="currentMode"
@@ -3,7 +3,8 @@ import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js'; import ApiLvPlan from '../../../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js'; import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week' export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week";
export const DEFAULT_MODE_LVPLAN_MOBILE = "List";
export default { export default {
name: 'LvPlanPersonal', name: 'LvPlanPersonal',
@@ -11,7 +12,6 @@ export default {
FhcCalendar FhcCalendar
}, },
props: { props: {
viewData: Object, // NOTE(chris): this is inherited from router-view
propsViewData: Object propsViewData: Object
}, },
data() { data() {
@@ -21,18 +21,30 @@ export default {
studiensemester_ende: null, studiensemester_ende: null,
uid: null, uid: null,
isMitarbeiter: false, isMitarbeiter: false,
isStudent: false isStudent: false,
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
}; };
}, },
inject: ["isMobile"],
computed:{ computed:{
currentDay() { currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date))) if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate(); return luxon.DateTime.now().setZone(this.timezone).toISODate();
return this.propsViewData?.focus_date; return this.propsViewData?.focus_date;
}, },
currentMode() { currentMode() {
if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase())) let validModes = ["day", "month"];
return DEFAULT_MODE_LVPLAN; validModes.push(this.isMobile ? "list" : "week");
const defaultMode = this.isMobile
? DEFAULT_MODE_LVPLAN_MOBILE
: DEFAULT_MODE_LVPLAN_DESKTOP;
if (
!this.propsViewData?.mode ||
!validModes.includes(this.propsViewData?.mode.toLowerCase())
)
return defaultMode;
return this.propsViewData?.mode; return this.propsViewData?.mode;
}, },
downloadLinks() { downloadLinks() {
@@ -47,7 +59,7 @@ export default {
return; return;
} }
const opts = { zone: this.viewData.timezone }; const opts = { zone: this.timezone };
const start = luxon.DateTime const start = luxon.DateTime
.fromISO(this.studiensemester_start, opts) .fromISO(this.studiensemester_start, opts)
.toUnixInteger(); .toUnixInteger();
@@ -70,6 +82,17 @@ export default {
]; ];
} }
}, },
watch: {
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: { methods: {
handleChangeDate(day, newMode) { handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day); return this.handleChangeMode(newMode, day);
@@ -102,16 +125,18 @@ export default {
this.$api.call(ApiLvPlan.eventsPersonal(start.toISODate(), end.toISODate())), this.$api.call(ApiLvPlan.eventsPersonal(start.toISODate(), end.toISODate())),
this.$api.call(ApiLvPlan.getLvPlanReservierungen(start.toISODate(), end.toISODate())) this.$api.call(ApiLvPlan.getLvPlanReservierungen(start.toISODate(), end.toISODate()))
]; ];
} },
async fetchAuthInfo() {
const authInfoResponse = await this.$api.call(ApiAuthinfo.getAuthInfo());
const authInfo = authInfoResponse.data;
this.uid = authInfo.uid;
this.isMitarbeiter = authInfo.isMitarbeiter;
this.isStudent = authInfo.isStudent;
},
}, },
created() { async created() {
this.$api await this.fetchAuthInfo();
.call(ApiAuthinfo.getAuthInfo())
.then(res => {
this.uid = res.data.uid;
this.isMitarbeiter = res.data.isMitarbeiter;
this.isStudent = res.data.isStudent;
});
}, },
template: /*html*/` template: /*html*/`
<div class="cis-lvplan-personal d-flex flex-column h-100"> <div class="cis-lvplan-personal d-flex flex-column h-100">
@@ -123,8 +148,9 @@ export default {
</h2> </h2>
<hr> <hr>
<fhc-calendar <fhc-calendar
v-if="timezone"
ref="calendar" ref="calendar"
:timezone="viewData.timezone" :timezone="timezone"
:get-promise-func="getPromiseFunc" :get-promise-func="getPromiseFunc"
:date="currentDay" :date="currentDay"
:mode="currentMode" :mode="currentMode"
@@ -0,0 +1,281 @@
import FormForm from "../../Form/Form.js";
import FormInput from "../../Form/Input.js";
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from "../.././../api/factory/lvPlan.js";
import ApiOtherLvPlan from "../.././../api/factory/otherLvPlan.js";
import ApiAuthinfo from "../../../api/factory/authinfo.js";
export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week";
export const DEFAULT_MODE_LVPLAN_MOBILE = "List";
export default {
name: "OtherLvPlan",
components: {
FormForm,
FormInput,
FhcCalendar,
},
props: {
propsViewData: Object,
},
data() {
return {
localProps: {},
studiensemester_kurzbz: null,
studiensemester_start: null,
studiensemester_ende: null,
isOtherPersonMitarbeiter: false,
isOtherPersonStudent: false,
currentStgBezeichnung: null,
listVerband: [],
listGroup: [],
rangeIntervalFirst: null,
otherPersonData: {
fullName: "",
photo: "",
},
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
};
},
inject: ["isMobile"],
computed: {
currentDay() {
if (
!this.propsViewData?.focus_date ||
isNaN(new Date(this.propsViewData?.focus_date))
)
return luxon.DateTime.now().setZone(this.timezone).toISODate();
return this.propsViewData?.focus_date;
},
currentMode() {
let validModes = ["day", "month"];
validModes.push(this.isMobile ? "list" : "week");
const defaultMode = this.isMobile
? DEFAULT_MODE_LVPLAN_MOBILE
: DEFAULT_MODE_LVPLAN_DESKTOP;
if (
!this.propsViewData?.mode ||
!validModes.includes(this.propsViewData?.mode.toLowerCase())
)
return defaultMode;
return this.propsViewData?.mode;
},
downloadLinks() {
// do not show download links in otherLvPlan Mode
return false;
if (
!this.studiensemester_start ||
!this.studiensemester_ende ||
!this.propsViewData.otherUid
)
return false;
const type = this.isOtherPersonStudent
? "student"
: this.isOtherPersonMitarbeiter
? "lektor"
: null;
if (!type) return;
const opts = { zone: this.timezone };
const start = luxon.DateTime.fromISO(
this.studiensemester_start,
opts,
).toUnixInteger();
const ende = luxon.DateTime.fromISO(
this.studiensemester_ende,
opts,
).toUnixInteger();
const download_link =
FHC_JS_DATA_STORAGE_OBJECT.app_root +
"cis/private/lvplan/stpl_kalender.php" +
"?type=" +
type +
"&pers_uid=" +
this.propsViewData.otherUid +
"&begin=" +
start +
"&ende=" +
ende;
return [
{
title: "excel",
icon: "fa-solid fa-file-excel",
link: download_link + "&format=excel",
},
{
title: "csv",
icon: "fa-solid fa-file-csv",
link: download_link + "&format=csv",
},
{
title: "ical1",
icon: "fa-regular fa-calendar",
link: download_link + "&format=ical&version=1&target=ical",
},
{
title: "ical2",
icon: "fa-regular fa-calendar",
link: download_link + "&format=ical&version=2&target=ical",
},
];
},
get_image_base64_src: function () {
if (!this.otherPersonData.photo?.length) {
return "";
}
return "data:image/jpeg;base64," + this.otherPersonData.photo;
},
},
watch: {
"propsViewData.otherUid": {
handler() {
this.$router.go();
},
},
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: {
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
},
handleChangeMode(newMode, day) {
const mode = newMode[0].toUpperCase() + newMode.slice(1);
const focus_date = day.toISODate();
this.$router.push({
name: "OtherLvPlan",
params: {
mode,
focus_date,
},
});
},
updateRange(rangeInterval) {
this.$api
.call(
ApiLvPlan.studiensemesterDateInterval(
rangeInterval.end.startOf("week").toISODate(),
),
)
.then((res) => {
this.studiensemester_kurzbz =
res.data.studiensemester_kurzbz;
this.studiensemester_start = res.data.start;
this.studiensemester_ende = res.data.ende;
});
},
getPromiseFunc(start, end) {
return [
this.$api.call(
ApiLvPlan.eventsPersonal(
start.toISODate(),
end.toISODate(),
this.propsViewData.otherUid,
),
),
this.$api.call(
ApiLvPlan.getLvPlanReservierungen(
start.toISODate(),
end.toISODate(),
this.propsViewData.otherUid,
),
),
];
},
async fetchViewData() {
const viewDataResponse = await this.$api.call(
ApiOtherLvPlan.getOtherLvPlanViewData(
this.propsViewData.otherUid,
),
);
const viewData = viewDataResponse.data;
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;
this.otherPersonData.photo = viewData?.user_data?.foto;
},
async redirectToMyLvPlanIfAuthUid() {
const authInfoResponse = await this.$api.call(
ApiAuthinfo.getAuthInfo(),
);
const authId = authInfoResponse.data.uid;
if (authId === this.propsViewData.otherUid) {
this.$router.push({ name: "MyLvPlan" });
}
},
},
async created() {
await this.redirectToMyLvPlanIfAuthUid();
await this.fetchViewData();
},
template: `
<div class="d-flex flex-column h-100">
<h2>
<div class="d-flex flex-row justify-content-between align-items-center">
<span>
{{ $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>
<img v-if="otherPersonData.photo?.length" alt="profile picture" class=" img-thumbnail " style=" max-height:60px; " :src="get_image_base64_src"/>
</div>
</div>
</h2>
<hr>
<fhc-calendar
v-if="timezone"
ref="calendar"
:timezone="timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@update:range="updateRange"
class="responsive-calendar"
>
<div
v-if="downloadLinks"
class="d-flex gap-1 justify-items-start"
>
<div v-for="{ title, icon, link } in downloadLinks">
<a
:href="link"
:aria-label="title"
class="py-1 btn btn-outline-secondary"
>
<div class="d-flex flex-column">
<i aria-hidden="true" :class="icon"></i>
<span style="font-size:.5rem">{{ title }}</span>
</div>
</a>
</div>
</div>
</fhc-calendar>
</div>
`,
};

Some files were not shown because too many files have changed in this diff Show More