Compare commits

..

68 Commits

Author SHA1 Message Date
adisposkofh fa91e204f0 always displaying timeslot on calendar event in list view 2026-05-26 12:02:08 +02:00
adisposkofh 23506430b1 initializing stg org lv plan with provided url params 2026-05-26 09:30:50 +02:00
adisposkofh fa58635a22 positioning of profile cards 'quick links' and 'calendar sync' 2026-05-26 09:29:10 +02:00
Harald Bamberger 7f630f24d5 StundenplanLib: check array index access before 2026-05-26 07:54:41 +02:00
Harald Bamberger c0c57ba378 remove irregular files 2026-05-22 10:36:28 +02:00
Harald Bamberger 449537ef77 Merge branch 'master' into cis40_2026-02_rc 2026-05-18 07:26:37 +02:00
adisposkofh d16120f650 Merge branch 'cis40_2026-02_rc' of github.com:FH-Complete/FHC-Core into cis40_2026-02_rc 2026-05-15 17:47:01 +02:00
Harald Bamberger 12d8c8447b Merge branch 'feature-68957/CIS4_Dashboard_Bookmark_Widget_Sort_Tags' into cis40_2026-02_rc 2026-05-13 21:10:27 +02:00
Harald Bamberger 905cd46942 add condition converting column tbl_bookmark.tag to jsonb 2026-05-13 21:09:18 +02:00
adisposkofh 625ffe12ce added 'info' marker to clickable multiple grades 2026-05-13 16:46:23 +02:00
Harald Bamberger cb7a0f7669 Merge branch 'feature-70376/Lohnguide' 2026-05-13 11:53:14 +02:00
Harald Bamberger 68d97a5e97 handle case where old value or new value and not both are null explicitly in markDirty Method 2026-05-13 11:42:25 +02:00
Harald Bamberger d27071528f revert change to comparision in markDirty Method 2026-05-13 11:16:18 +02:00
Harald Bamberger 17772c3738 Merge branch 'master' into feature-70376/Lohnguide 2026-05-13 11:15:07 +02:00
adisposkofh 772f35c6ba reworked sidenav toggler 2026-05-11 13:00:50 +02:00
adisposkofh 4b22f939b5 reworked searchbar collapse for component reusability 2026-05-11 10:24:18 +02:00
adisposkofh 7b46a15752 minor fix 2026-05-11 09:58:33 +02:00
adisposkofh bf3d6275d4 header rework: single fixed element, ordering, small screen cutoff 2026-05-08 16:31:55 +02:00
adisposkofh dd2fd6421b clean lv plan reactions to ismobile changes 2026-05-07 17:23:28 +02:00
adisposkofh 3b99a14b47 clean lv plan reactions to ismobile changes 2026-05-07 17:18:33 +02:00
adisposkofh 1d3d067b44 making isMobile property reactive to window resizing 2026-05-07 16:39:07 +02:00
adisposkofh d3ceed32c6 compacting all events in mobile monthly lv plan 2026-05-07 15:15:50 +02:00
adisposkofh 780890fbdd made search icons white in both dark and light mode 2026-05-07 14:19:47 +02:00
adisposkofh 72aed76857 added border to compacted events in lv plan for color contrasting 2026-05-07 14:06:23 +02:00
adisposkofh 229882e8d8 Merge branch 'cis40_2026-02_rc' of github.com:FH-Complete/FHC-Core into cis40_2026-02_rc 2026-05-07 13:19:49 +02:00
adisposkofh 1e184d36fc resolved prop type/injection-related warnings in 'my courses' 2026-05-07 11:10:01 +02:00
Harald Bamberger 90e933de00 Merge branch 'master' into cis40_2026-02_rc 2026-05-06 17:35:28 +02:00
adisposkofh 14372a6fce fixed multiple issues in news 2026-05-06 16:21:20 +02:00
Harald Bamberger bbb4f8a01c Merge branch 'bug-76146/studvw_karteireiter_dokumente_akzeptiert_eintraege_ohne_vorhandenes_dokument' 2026-05-06 16:13:50 +02:00
adisposkofh 80306dadf7 fixed issue with missing nullsafe operators 2026-05-06 15:29:33 +02:00
adisposkofh adba14f6e7 avoiding search when search string is empty 2026-05-06 13:49:43 +02:00
adisposkofh 57e901be27 other_lv_plan permission on reservations 2026-05-06 13:41:16 +02:00
adisposkofh d38641e312 displaying student personal identity number and matriculation number 2026-05-06 13:22:38 +02:00
Harald Bamberger fdbb93a5c5 bugfix download booking receipt failed. only fetch oehbeitrag from bis.tbl_oehbeitrag if a user is logged in 2026-05-05 14:36:07 +02:00
Harald Bamberger b7e48633ab Merge branch 'master' into bug-76146/studvw_karteireiter_dokumente_akzeptiert_eintraege_ohne_vorhandenes_dokument 2026-05-05 13:33:38 +02:00
Harald Bamberger 04dc1eb07b Merge branch 'bug-76519/StudVW_Messages_Table_column_Stati' 2026-05-05 13:04:58 +02:00
Harald Bamberger 50b229090b prefetch phrases and then render filter component instead of redrawing the table 2026-05-05 13:04:11 +02:00
Harald Bamberger 86dc002fa6 add composer script to symlink vendor folder in public to be able to import esm modules from vendor without breaking rewrite mechanism for cache token in urlpath 2026-05-05 12:36:37 +02:00
Harald Bamberger 2d27a998c4 Merge branch 'bug-76109/VVW_Details_und_Status_not_Loading' 2026-05-05 11:17:49 +02:00
Harald Bamberger 090e535466 add header filters, increase height of tables, use correct category for phrase lehreinheit_id 2026-05-05 11:04:48 +02:00
Harald Bamberger c4d35181db Merge branch 'master' into bug-76109/VVW_Details_und_Status_not_Loading 2026-05-05 09:01:49 +02:00
Harald Bamberger 453fc209b8 Merge branch 'master' into cis40_2026-02_rc 2026-05-05 08:53:07 +02:00
Harald Bamberger 0ac6ef4599 Merge branch 'feature-62607/konto_oh_beitrag_betrag_aus_eigener_tabelle' 2026-05-05 08:46:22 +02:00
Werner Masik 7f13c128f1 allow null value for vordienstzeit; changed comparison in markDirty to !== (because of 0 vs. null issue) 2026-05-04 20:35:51 +02:00
Harald Bamberger cb60ddcc94 Merge branch 'master' into feature-62607/konto_oh_beitrag_betrag_aus_eigener_tabelle 2026-05-04 15:41:52 +02:00
Harald Bamberger bd4ced9559 bugfix: comma as decimal separator prevents saving booking, bugfix messages tinymce not resizeable 2026-05-04 14:39:19 +02:00
Andreas Österreicher a04d2acb86 Fixed Blank on Phrase for Abgabetool 2026-05-04 10:49:33 +02:00
Harald Bamberger de2aabf00b readd dokument preview link to api response 2026-05-04 09:30:16 +02:00
ma0068 685fc69e5d update css and add provisional height 2026-04-30 18:02:38 +02:00
Werner Masik 58a921b500 changed lohnguide kommentar data type to text 2026-04-30 09:47:12 +02:00
Harald Bamberger af8814468f Merge branch 'feature-76669/optimizing-menu-load' into cis40_2026-02_rc 2026-04-30 07:43:56 +02:00
Harald Bamberger c8a6e2f7cd Merge branch 'master' into cis40_2026-02_rc 2026-04-30 07:35:29 +02:00
Harald Bamberger 2227c3ecf1 Merge branch 'feature-76671/moving-avg-grade-calc-to-backend' into cis40_2026-02_rc 2026-04-30 07:33:42 +02:00
Harald Bamberger c7526bd0d9 Merge branch 'feature-76385/mobile-optimization-room-search-page' into cis40_2026-02_rc 2026-04-30 07:28:55 +02:00
Harald Bamberger 791f69b509 Merge branch 'feature-76385/mobile-optimization-profile-tabulator' into cis40_2026-02_rc 2026-04-30 07:09:56 +02:00
adisposkofh 5aded99999 code formatting 2026-04-24 15:56:39 +02:00
adisposkofh f780553773 moved avg grade calcs to backend 2026-04-24 15:55:03 +02:00
adisposkofh 868599a7fe fixed profile tables headings localization 2026-04-24 10:50:32 +02:00
adisposkofh 331381c94d removed obsolete comments 2026-04-23 15:05:54 +02:00
adisposkofh 478b23825c fixed broken formatting in collapsing profile tabulators 2026-04-23 15:05:21 +02:00
adisposkofh 956b201757 minor fix 2026-04-21 11:25:48 +02:00
adisposkofh e5015f348b reworked roomsearch inputs spacing 2026-04-20 17:07:49 +02:00
adisposkofh 6792002c19 added spacing between room search inputs, mostly visible in mobile 2026-04-20 17:03:05 +02:00
adisposkofh 9890f6aade fixed issue: context menu appearing on vue datepicker single click in mobile 2026-04-20 16:58:43 +02:00
ma0068 26db4a5e7a adding redraw tabulator and fallback to avoid empty column 2026-04-20 09:31:52 +02:00
ma0048 21d80905a2 akzeptierte dokumente anzeigen, auch wenn kein dokument vorhanden ist 2026-04-13 13:04:46 +02:00
ma0068 298dbbf400 use context vm for tabulator event, refactor function toggleRowClick, delete unused commented sections 2026-04-02 10:58:49 +02:00
ma0048 ba6224bc78 oeh betrag aus der eigener tabelle holen
studentenverwaltung bei jedem studiensemester wechsel
fas nur einmalig ueber die variable
2025-09-02 11:18:24 +02:00
57 changed files with 892 additions and 1329 deletions
+2 -1
View File
@@ -22,8 +22,9 @@ unset($config['student']['searchfields']['email']);
unset($config['student']['searchfields']['tel']);
$config['student']['resultfields'] = [
"s.student_uid AS uid",
"s.matrikelnr",
"s.matrikelnr AS personenkennzeichen",
"p.person_id",
"p.matr_nr AS matrikelnummer",
"(p.vorname || ' ' || p.nachname) AS name",
"ARRAY[s.student_uid || '@' || '" . DOMAIN . "'] AS email",
"CASE
@@ -38,7 +38,8 @@ class Lehre extends FHCAPI_Controller
parent::__construct([
'lvStudentenMail' => 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);
}
/**
* 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]);
}
}
@@ -99,7 +99,7 @@ class LvPlan extends FHCAPI_Controller
$end_date = $this->input->post('end_date', true);
$uid = $this->input->post('uid', true);
// disallow accessing other user's lv plan if missing permission
// 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!");
}
@@ -109,7 +109,7 @@ class LvPlan extends FHCAPI_Controller
$lvplanEvents = $this->getDataOrTerminateWithError($result);
// fetching moodle events
$moodleEvents = $uid ? [] : $this->fetchMoodleEvents($start_date, $end_date);
$moodleEvents = $this->fetchMoodleEvents($start_date, $end_date, $uid);
// fetching ferien events
$ferienEvents = $this->fetchFerienEvents($start_date, $end_date, $uid);
@@ -288,6 +288,11 @@ class LvPlan extends FHCAPI_Controller
$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
$this->load->library('StundenplanLib');
@@ -401,7 +406,7 @@ class LvPlan extends FHCAPI_Controller
*/
public function compactibleEventTypes()
{
$this->terminateWithSuccess(["lehreinheit", "reservierung"]);
$this->terminateWithSuccess(["lehreinheit", "reservierung", "ferien", "moodle"]);
}
/**
@@ -411,7 +416,7 @@ class LvPlan extends FHCAPI_Controller
* @param string $end_date
* @return array
*/
private function fetchMoodleEvents($start_date, $end_date)
private function fetchMoodleEvents($start_date, $end_date, $uid = null)
{
$this->load->config('calendar');
@@ -434,7 +439,7 @@ class LvPlan extends FHCAPI_Controller
[
'start_date' => $start->format('c'),
'end_date' => $end->format('c'),
'username' => getAuthUID()
'username' => $uid ?? getAuthUID()
]
);
@@ -78,52 +78,32 @@ class Dokumente extends FHCAPI_Controller
$this->terminateWithError($this->p->t('ui', 'errorMissingValue', ['value' => 'Studiengang_kz']), self::ERROR_TYPE_GENERAL);
$resultPreDoc = $this->_getPrestudentDokumente($prestudent_id);
$arrayAccepted = [];
$person_id = $this->_getPersonId($prestudent_id);
$docNames = array_map(function ($item) {
return $item->dokument_kurzbz;
}, $resultPreDoc);
$mergedArray = [];
foreach($docNames as $doc)
foreach ($resultPreDoc as $pre)
{
$result = $this->AkteModel->getAktenFAS($person_id, $doc, $studiengang_kz, $prestudent_id, true);
$result = $this->AkteModel->getAktenFAS($person_id, $pre->dokument_kurzbz, $studiengang_kz, $prestudent_id, true);
if (isError($result))
{
return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
}
if (hasData($result))
{
$data = getData($result);
foreach ($data as $value)
foreach (getData($result) as $doc)
{
array_push($arrayAccepted, $value);
$merged = clone $doc;
$merged->docdatum = $pre->docdatum;
$merged->insertvonma = $pre->insertvonma;
$merged->bezeichnung = $pre->bezeichnung;
$mergedArray[] = $merged;
}
}
}
//Mapping with document_kurzbz
$preDocMap = [];
foreach ($resultPreDoc as $pre) {
$preDocMap[$pre->dokument_kurzbz] = $pre;
}
$mergedArray = [];
foreach ($arrayAccepted as $doc) {
$merged = clone $doc;
if (isset($preDocMap[$doc->dokument_kurzbz])) {
$merged->docdatum = $preDocMap[$doc->dokument_kurzbz]->docdatum;
$merged->insertvonma = $preDocMap[$doc->dokument_kurzbz]->insertvonma;
$merged->bezeichnung = $preDocMap[$doc->dokument_kurzbz]->bezeichnung;
} else {
$merged->akzeptiertdatum = null;
$merged->akzeptiertvon = null;
else
{
$mergedArray[] = $pre;
}
$mergedArray[] = $merged;
}
$this->terminateWithSuccess($mergedArray);
@@ -48,7 +48,8 @@ class Konto extends FHCAPI_Controller
// Load language phrases
$this->loadPhrases([
'konto'
'konto',
'lehre'
]);
}
@@ -112,7 +113,7 @@ class Konto extends FHCAPI_Controller
*
* @return void
*/
public function getBuchungstypen()
public function getBuchungstypen($studiensemester_kurzbz = null)
{
$this->load->model('crm/Buchungstyp_model', 'BuchungstypModel');
@@ -122,6 +123,7 @@ class Konto extends FHCAPI_Controller
$data = $this->getDataOrTerminateWithError($result);
$this->_getOEHBeitrag($data, $studiensemester_kurzbz);
$this->terminateWithSuccess($data);
}
@@ -494,4 +496,43 @@ class Konto extends FHCAPI_Controller
$this->terminateWithSuccess();
}
private function _getOEHBeitrag(&$data, $studiensemester_kurzbz = null)
{
if (is_null($studiensemester_kurzbz))
{
$this->load->library('VariableLib', ['uid' => getAuthUID()]);
$studiensemester_akt = $this->variablelib->getVar('semester_aktuell');
}
else
{
$this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
if ($this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz))
$studiensemester_akt = $studiensemester_kurzbz;
else
$this->terminateWithError($this->p->t('lehre', 'error_noStudiensemester'));
}
$this->load->model('codex/Oehbeitrag_model', 'OehbeitragModel');
$oehBeitrag = $this->OehbeitragModel->getByStudiensemester($studiensemester_akt);
$oehStandardbetrag = null;
if (hasData($oehBeitrag))
{
$oeh = getData($oehBeitrag)[0];
$summe = ($oeh->studierendenbeitrag + $oeh->versicherung) * -1;
$oehStandardbetrag = number_format((float)$summe, 2, '.', '');
}
if ($oehStandardbetrag !== null)
{
$data = array_map(function ($buchungstyp) use ($oehStandardbetrag) {
if (isset($buchungstyp->buchungstyp_kurzbz) && (strtolower($buchungstyp->buchungstyp_kurzbz) === 'oeh'))
{
$buchungstyp->standardbetrag = $oehStandardbetrag;
}
return $buchungstyp;
}, $data);
}
}
}
+1
View File
@@ -417,6 +417,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller
$notiz_id = $this->input->post('notiz_id');
$this->NotizModel->addSelect('campus.tbl_dms_version.*');
$this->NotizModel->addSelect($this->NotizModel->escape(base_url('content/notizdokdownload.php?id=')) . ' || public.tbl_notiz_dokument.dms_id AS preview');
$this->NotizModel->addJoin('public.tbl_notiz_dokument', 'ON (public.tbl_notiz_dokument.notiz_id = public.tbl_notiz.notiz_id)');
$this->NotizModel->addJoin('campus.tbl_dms_version', 'ON (public.tbl_notiz_dokument.dms_id = campus.tbl_dms_version.dms_id)');
-517
View File
@@ -1,517 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| Base Site URL
|--------------------------------------------------------------------------
|
| URL to your CodeIgniter root. Typically this will be your base URL,
| WITH a trailing slash:
|
| http://example.com/
|
| If this is not set then CodeIgniter will try guess the protocol, domain
| and path to your installation. However, you should always configure this
| explicitly and never rely on auto-guessing, especially in production
| environments.
|
*/
$config['base_url'] = 'https://c3p0.dev.technikum-wien.at/ma1434/core/FHC-Core/';
/*
|--------------------------------------------------------------------------
| Index File
|--------------------------------------------------------------------------
|
| Typically this will be your index.php file, unless you've renamed it to
| something else. If you are using mod_rewrite to remove the page set this
| variable so that it is blank.
|
*/
$config['index_page'] = 'index.ci.php';
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which server global should be used to retrieve the
| URI string. The default setting of 'REQUEST_URI' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
| 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
| 'PATH_INFO' Uses $_SERVER['PATH_INFO']
|
| WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
*/
$config['uri_protocol'] = 'REQUEST_URI';
/*
|--------------------------------------------------------------------------
| URL suffix
|--------------------------------------------------------------------------
|
| This option allows you to add a suffix to all URLs generated by CodeIgniter.
| For more information please see the user guide:
|
| http://codeigniter.com/user_guide/general/urls.html
*/
$config['url_suffix'] = '';
/*
|--------------------------------------------------------------------------
| Default Language
|--------------------------------------------------------------------------
|
| This determines which set of language files should be used. Make sure
| there is an available translation if you intend to use something other
| than english.
|
*/
$config['language'] = '';
/*
|--------------------------------------------------------------------------
| Default Character Set
|--------------------------------------------------------------------------
|
| This determines which character set is used by default in various methods
| that require a character set to be provided.
|
| See http://php.net/htmlspecialchars for a list of supported charsets.
|
*/
$config['charset'] = 'UTF-8';
/*
|--------------------------------------------------------------------------
| Enable/Disable System Hooks
|--------------------------------------------------------------------------
|
| If you would like to use the 'hooks' feature you must enable it by
| setting this variable to TRUE (boolean). See the user guide for details.
|
*/
$config['enable_hooks'] = FALSE;
/*
|--------------------------------------------------------------------------
| Class Extension Prefix
|--------------------------------------------------------------------------
|
| This item allows you to set the filename/classname prefix when extending
| native libraries. For more information please see the user guide:
|
| http://codeigniter.com/user_guide/general/core_classes.html
| http://codeigniter.com/user_guide/general/creating_libraries.html
|
*/
$config['subclass_prefix'] = 'FHC_';
/*
|--------------------------------------------------------------------------
| Composer auto-loading
|--------------------------------------------------------------------------
|
| Enabling this setting will tell CodeIgniter to look for a Composer
| package auto-loader script in application/vendor/autoload.php.
|
| $config['composer_autoload'] = TRUE;
|
| Or if you have your vendor/ directory located somewhere else, you
| can opt to set a specific path as well:
|
| $config['composer_autoload'] = '/path/to/vendor/autoload.php';
|
| For more information about Composer, please visit http://getcomposer.org/
|
| Note: This will NOT disable or override the CodeIgniter-specific
| autoloading (application/config/autoload.php)
*/
$config['composer_autoload'] = FALSE;
/*
|--------------------------------------------------------------------------
| Allowed URL Characters
|--------------------------------------------------------------------------
|
| This lets you specify which characters are permitted within your URLs.
| When someone tries to submit a URL with disallowed characters they will
| get a warning message.
|
| As a security measure you are STRONGLY encouraged to restrict URLs to
| as few characters as possible. By default only these are allowed: a-z 0-9~%.:_-
|
| Leave blank to allow all characters -- but only if you are insane.
|
| The configured value is actually a regular expression character group
| and it will be executed as: ! preg_match('/^[<permitted_uri_chars>]+$/i
|
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
*/
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-';
/*
|--------------------------------------------------------------------------
| Enable Query Strings
|--------------------------------------------------------------------------
|
| By default CodeIgniter uses search-engine friendly segment based URLs:
| example.com/who/what/where/
|
| By default CodeIgniter enables access to the $_GET array. If for some
| reason you would like to disable it, set 'allow_get_array' to FALSE.
|
| You can optionally enable standard query string based URLs:
| example.com?who=me&what=something&where=here
|
| Options are: TRUE or FALSE (boolean)
|
| The other items let you set the query string 'words' that will
| invoke your controllers and its functions:
| example.com/index.php?c=controller&m=function
|
| Please note that some of the helpers won't work as expected when
| this feature is enabled, since CodeIgniter is designed primarily to
| use segment based URLs.
|
*/
$config['allow_get_array'] = TRUE;
$config['enable_query_strings'] = FALSE;
$config['controller_trigger'] = 'c';
$config['function_trigger'] = 'm';
$config['directory_trigger'] = 'd';
/*
|--------------------------------------------------------------------------
| Error Logging Threshold
|--------------------------------------------------------------------------
|
| You can enable error logging by setting a threshold over zero. The
| threshold determines what gets logged. Threshold options are:
|
| 0 = Disables logging, Error logging TURNED OFF
| 1 = Error Messages (including PHP errors)
| 2 = Debug Messages
| 3 = Informational Messages
| 4 = All Messages
|
| You can also pass an array with threshold levels to show individual error types
|
| array(2) = Debug Messages, without Error Messages
|
| For a live site you'll usually only enable Errors (1) to be logged otherwise
| your log files will fill up very fast.
|
*/
$config['log_threshold'] = 1;
/*
|--------------------------------------------------------------------------
| Error Logging Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/logs/ directory. Use a full server path with trailing slash.
|
*/
$config['log_path'] = '';
/*
|--------------------------------------------------------------------------
| Log File Extension
|--------------------------------------------------------------------------
|
| The default filename extension for log files. The default 'php' allows for
| protecting the log files via basic scripting, when they are to be stored
| under a publicly accessible directory.
|
| Note: Leaving it blank will default to 'php'.
|
*/
$config['log_file_extension'] = 'log';
/*
|--------------------------------------------------------------------------
| Log File Permissions
|--------------------------------------------------------------------------
|
| The file system permissions to be applied on newly created log files.
|
| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal
| integer notation (i.e. 0700, 0644, etc.)
*/
$config['log_file_permissions'] = 0644;
/*
|--------------------------------------------------------------------------
| Date Format for Logs
|--------------------------------------------------------------------------
|
| Each item that is logged has an associated date. You can use PHP date
| codes to set your own date formatting
|
*/
$config['log_date_format'] = 'Y-m-d H:i:s';
/*
|--------------------------------------------------------------------------
| Error Views Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/views/errors/ directory. Use a full server path with trailing slash.
|
*/
$config['error_views_path'] = '';
/*
|--------------------------------------------------------------------------
| Cache Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/cache/ directory. Use a full server path with trailing slash.
|
*/
$config['cache_path'] = '';
/*
|--------------------------------------------------------------------------
| Cache Include Query String
|--------------------------------------------------------------------------
|
| Whether to take the URL query string into consideration when generating
| output cache files. Valid options are:
|
| FALSE = Disabled
| TRUE = Enabled, take all query parameters into account.
| Please be aware that this may result in numerous cache
| files generated for the same page over and over again.
| array('q') = Enabled, but only take into account the specified list
| of query parameters.
|
*/
$config['cache_query_string'] = FALSE;
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| If you use the Encryption class, you must set an encryption key.
| See the user guide for more info.
|
| http://codeigniter.com/user_guide/libraries/encryption.html
|
*/
$config['encryption_key'] = '';
/*
|--------------------------------------------------------------------------
| Session Variables
|--------------------------------------------------------------------------
|
| 'sess_driver'
|
| The storage driver to use: files, database, redis, memcached
|
| 'sess_cookie_name'
|
| The session cookie name, must contain only [0-9a-z_-] characters
|
| 'sess_expiration'
|
| The number of SECONDS you want the session to last.
| Setting to 0 (zero) means expire when the browser is closed.
|
| 'sess_save_path'
|
| The location to save sessions to, driver dependent.
|
| For the 'files' driver, it's a path to a writable directory.
| WARNING: Only absolute paths are supported!
|
| For the 'database' driver, it's a table name.
| Please read up the manual for the format with other session drivers.
|
| IMPORTANT: You are REQUIRED to set a valid save path!
|
| 'sess_match_ip'
|
| Whether to match the user's IP address when reading the session data.
|
| 'sess_time_to_update'
|
| How many seconds between CI regenerating the session ID.
| NOTE: Keep it as it is to prevent security issues (https://en.wikipedia.org/wiki/Session_fixation)
|
| 'sess_regenerate_destroy'
|
| Whether to destroy session data associated with the old session ID
| when auto-regenerating the session ID. When set to FALSE, the data
| will be later deleted by the garbage collector.
|
| Other session cookie settings are shared with the rest of the application,
| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here.
|
*/
$config['sess_driver'] = 'files';
$config['sess_cookie_name'] = 'sess_ci_session';
$config['sess_expiration'] = 1800; // Session expires every 30 minutes
$config['sess_save_path'] = NULL;
$config['sess_match_ip'] = FALSE;
$config['sess_time_to_update'] = 300;
$config['sess_regenerate_destroy'] = FALSE;
/*
|--------------------------------------------------------------------------
| Cookie Related Variables
|--------------------------------------------------------------------------
|
| 'cookie_prefix' = Set a cookie name prefix if you need to avoid collisions
| 'cookie_domain' = Set to .your-domain.com for site-wide cookies
| 'cookie_path' = Typically will be a forward slash
| 'cookie_secure' = Cookie will only be set if a secure HTTPS connection exists.
| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript)
|
| Note: These settings (with the exception of 'cookie_prefix' and
| 'cookie_httponly') will also affect sessions.
|
*/
$config['cookie_prefix'] = '';
$config['cookie_domain'] = '';
$config['cookie_path'] = '/';
$config['cookie_secure'] = FALSE;
$config['cookie_httponly'] = FALSE;
/*
|--------------------------------------------------------------------------
| Standardize newlines
|--------------------------------------------------------------------------
|
| Determines whether to standardize newline characters in input data,
| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value.
|
| This is particularly useful for portability between UNIX-based OSes,
| (usually \n) and Windows (\r\n).
|
*/
$config['standardize_newlines'] = FALSE;
/*
|--------------------------------------------------------------------------
| Global XSS Filtering
|--------------------------------------------------------------------------
|
| Determines whether the XSS filter is always active when GET, POST or
| COOKIE data is encountered
|
| WARNING: This feature is DEPRECATED and currently available only
| for backwards compatibility purposes!
|
*/
$config['global_xss_filtering'] = FALSE;
/*
|--------------------------------------------------------------------------
| Cross Site Request Forgery
|--------------------------------------------------------------------------
| Enables a CSRF cookie token to be set. When set to TRUE, token will be
| checked on a submitted form. If you are accepting user data, it is strongly
| recommended CSRF protection be enabled.
|
| 'csrf_token_name' = The token name
| 'csrf_cookie_name' = The cookie name
| 'csrf_expire' = The number in seconds the token should expire.
| 'csrf_regenerate' = Regenerate token on every submission
| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks
*/
$config['csrf_protection'] = FALSE;
$config['csrf_token_name'] = 'csrf_test_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array();
/*
|--------------------------------------------------------------------------
| Output Compression
|--------------------------------------------------------------------------
|
| Enables Gzip output compression for faster page loads. When enabled,
| the output class will test whether your server supports Gzip.
| Even if it does, however, not all browsers support compression
| so enable only if you are reasonably sure your visitors can handle it.
|
| Only used if zlib.output_compression is turned off in your php.ini.
| Please do not use it together with httpd-level output compression.
|
| VERY IMPORTANT: If you are getting a blank page when compression is enabled it
| means you are prematurely outputting something to your browser. It could
| even be a line of whitespace at the end of one of your scripts. For
| compression to work, nothing can be sent before the output buffer is called
| by the output class. Do not 'echo' any values with compression enabled.
|
*/
$config['compress_output'] = FALSE;
/*
|--------------------------------------------------------------------------
| Master Time Reference
|--------------------------------------------------------------------------
|
| Options are 'local' or any PHP supported timezone. This preference tells
| the system whether to use your server's local time as the master 'now'
| reference, or convert it to the configured one timezone. See the 'date
| helper' page of the user guide for information regarding date handling.
|
*/
$config['time_reference'] = 'local';
/*
|--------------------------------------------------------------------------
| Rewrite PHP Short Tags
|--------------------------------------------------------------------------
|
| If your PHP installation does not have short tag support enabled CI
| can rewrite the tags on-the-fly, enabling you to utilize that syntax
| in your view files. Options are TRUE or FALSE (boolean)
|
| Note: You need to have eval() enabled for this to work.
|
*/
$config['rewrite_short_tags'] = FALSE;
/*
|--------------------------------------------------------------------------
| Reverse Proxy IPs
|--------------------------------------------------------------------------
|
| If your server is behind a reverse proxy, you must whitelist the proxy
| IP addresses from which CodeIgniter should trust headers such as
| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
| the visitor's IP address.
|
| You can use both an array or a comma-separated list of proxy addresses,
| as well as specifying whole subnets. Here are a few examples:
|
| Comma-separated: '10.0.1.200,192.168.5.0/24'
| Array: array('10.0.1.200', '192.168.5.0/24')
*/
$config['proxy_ips'] = '';
/*
|--------------------------------------------------------------------------
| FHComplete Build Version
|--------------------------------------------------------------------------
|
| Version Number of the Current Build
| This is used to invalidate Cache for JS and CSS Files
|
| Example: 2019102901
*/
$config['fhcomplete_build_version'] = '2019102903';
-122
View File
@@ -1,122 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
defined('DB_HOST') OR require_once './config/system.config.inc.php'; // For CLI-Migrations
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the 'Database Connection'
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
| ['dsn'] The full DSN string describe a connection to the database.
| ['hostname'] The hostname of your database server.
| ['username'] The username used to connect to the database
| ['password'] The password used to connect to the database
| ['database'] The name of the database you want to connect to
| ['dbdriver'] The database driver. e.g.: mysqli.
| Currently supported:
| cubrid, ibase, mssql, mysql, mysqli, oci8,
| odbc, pdo, postgre, sqlite, sqlite3, sqlsrv
| ['dbprefix'] You can add an optional prefix, which will be added
| to the table name when using the Query Builder class
| ['pconnect'] TRUE/FALSE - Whether to use a persistent connection
| ['db_debug'] TRUE/FALSE - Whether database errors should be displayed.
| ['cache_on'] TRUE/FALSE - Enables/disables query caching
| ['cachedir'] The path to the folder where cache files should be stored
| ['char_set'] The character set used in communicating with the database
| ['dbcollat'] The character collation used in communicating with the database
| NOTE: For MySQL and MySQLi databases, this setting is only used
| as a backup if your server is running PHP < 5.2.3 or MySQL < 5.0.7
| (and in table creation queries made with DB Forge).
| There is an incompatibility in PHP with mysql_real_escape_string() which
| can make your site vulnerable to SQL injection if you are using a
| multi-byte character set and are running versions lower than these.
| Sites using Latin-1 or UTF-8 database character set and collation are unaffected.
| ['swap_pre'] A default table prefix that should be swapped with the dbprefix
| ['encrypt'] Whether or not to use an encrypted connection.
|
| 'mysql' (deprecated), 'sqlsrv' and 'pdo/sqlsrv' drivers accept TRUE/FALSE
| 'mysqli' and 'pdo/mysql' drivers accept an array with the following options:
|
| 'ssl_key' - Path to the private key file
| 'ssl_cert' - Path to the public key certificate file
| 'ssl_ca' - Path to the certificate authority file
| 'ssl_capath' - Path to a directory containing trusted CA certificats in PEM format
| 'ssl_cipher' - List of *allowed* ciphers to be used for the encryption, separated by colons (':')
| 'ssl_verify' - TRUE/FALSE; Whether verify the server certificate or not ('mysqli' only)
|
| ['compress'] Whether or not to use client compression (MySQL only)
| ['stricton'] TRUE/FALSE - forces 'Strict Mode' connections
| - good for ensuring strict SQL while developing
| ['ssl_options'] Used to set various SSL options that can be used when making SSL connections.
| ['failover'] array - A array with 0 or more data for connections if the main should fail.
| ['save_queries'] TRUE/FALSE - Whether to "save" all executed queries.
| NOTE: Disabling this will also effectively disable both
| $this->db->last_query() and profiling of DB queries.
| When you run a query, with this setting set to TRUE (default),
| CodeIgniter will store the SQL statement for debugging purposes.
| However, this may cause high memory usage, especially if you run
| a lot of SQL queries ... disable this to avoid that problem.
|
| The $active_group variable lets you choose which connection group to
| make active. By default there is only one group (the 'default' group).
|
| The $query_builder variables lets you determine whether or not to load
| the query builder class.
*/
$active_group = 'default';
$query_builder = TRUE;
$db['default'] = array(
'dsn' => '',
'hostname' => DB_HOST,
'username' => DB_USER,
'password' => DB_PASSWORD,
'port' => DB_PORT,
'database' => DB_NAME,
'dbdriver' => 'postgre',
'dbprefix' => '',
'pconnect' => DB_CONNECT_PERSISTENT,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
$db['system'] = array(
'dsn' => '',
'hostname' => DB_HOST,
'username' => 'fhcomplete',
'password' => 'Fhcomplet3Onc4p1',
'database' => DB_NAME,
'port' => DB_PORT,
'dbschema' => 'public',
'dbdriver' => 'postgre',
'dbprefix' => '',
'pconnect' => DB_CONNECT_PERSISTENT,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
+4 -1
View File
@@ -360,7 +360,10 @@ class StundenplanLib
if (isError($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) {
$item->ort_content_id = $ort_content_object->content_id;
}
@@ -40,7 +40,9 @@ abstract class AbstractBestandteil implements IValidation
if( is_bool($new_value) && ($old_value !== $new_value) ) {
$this->modifiedcolumns[$columnname] = $columnname;
} else if($old_value != $new_value) {
} else if(is_null($old_value) xor is_null($new_value)) {
$this->modifiedcolumns[$columnname] = $columnname;
} else if($old_value != $new_value) {
$this->modifiedcolumns[$columnname] = $columnname;
}
}
@@ -137,19 +137,25 @@ EOTXT;
return parent::__toString() . $txt;
}
/* public function validate()
public function validate()
{
if( !(filter_var($this->tage, FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1,
'max_range' => 50
)
)
)) ) {
$this->validationerrors[] = 'Urlaubsanspruch muss eine Tagesanzahl im Bereich 1 bis 50 sein.';
$value = $this->vordienstzeit;
if ($value === null || $value === '') {
$result = null; // allow null value
} else {
$result = filter_var($value, FILTER_VALIDATE_INT, [
'options' => [
'min_range' => 0,
'max_range' => 100
]
]);
if ($result === false) {
$this->validationerrors[] = 'Vordienstjahre muss eine ganze Zahl (0 bis 100) enthalten oder leer sein.';
}
}
return parent::validate();
} */
}
}
+4
View File
@@ -533,5 +533,9 @@
"phpmetrics/phpmetrics": "2.*",
"sebastian/phpcpd": "3.*",
"phpunit/phpunit": "^6"
},
"scripts": {
"post-install-cmd": "@symlink_vendor_to_public",
"symlink_vendor_to_public": "ln -sfn ../vendor ./public/"
}
}
+49 -1
View File
@@ -25,6 +25,7 @@
*/
require_once(dirname(__FILE__).'/basis_db.class.php');
require_once(dirname(__FILE__).'/'.EXT_FKT_PATH.'/generateZahlungsreferenz.inc.php');
require_once(dirname(__FILE__).'/variable.class.php');
class konto extends basis_db
{
@@ -432,6 +433,8 @@ class konto extends basis_db
$qry.=" ORDER BY beschreibung";
$oehBeitrag = $this->_getOEHBeitrag();
if($this->db_query($qry))
{
while($row = $this->db_fetch_object())
@@ -440,7 +443,15 @@ class konto extends basis_db
$typ->buchungstyp_kurzbz = $row->buchungstyp_kurzbz;
$typ->beschreibung = $row->beschreibung;
$typ->standardbetrag = $row->standardbetrag;
if (strtolower($typ->buchungstyp_kurzbz) === 'oeh' && $oehBeitrag)
{
$typ->standardbetrag = $oehBeitrag;
}
else
{
$typ->standardbetrag = $row->standardbetrag;
}
$typ->standardtext = $row->standardtext;
$typ->credit_points = $row->credit_points;
$typ->aktiv = $this->db_parse_bool($row->aktiv);
@@ -990,6 +1001,43 @@ class konto extends basis_db
return false;
}
}
private function _getOEHBeitrag()
{
if(!is_user_logged_in())
{
return false;
}
$variablen_obj = new variable();
$variablen_obj->loadVariables(get_uid());
$qry = "WITH semstart AS (
SELECT start FROM public.tbl_studiensemester
WHERE studiensemester_kurzbz = '". $this->db_escape($variablen_obj->variable->semester_aktuell) . "'
)
SELECT * FROM bis.tbl_oehbeitrag oehb
JOIN public.tbl_studiensemester semvon ON oehb.von_studiensemester_kurzbz = semvon.studiensemester_kurzbz
LEFT JOIN public.tbl_studiensemester sembis ON oehb.bis_studiensemester_kurzbz = sembis.studiensemester_kurzbz
JOIN semstart ON semstart.start::date >= semvon.start::date AND (sembis.studiensemester_kurzbz IS NULL OR semstart.start::date <= sembis.start::date)
ORDER BY semvon.start
LIMIT 1";
if ($this->db_query($qry))
{
if($row = $this->db_fetch_object())
{
$summe = ($row->studierendenbeitrag + $row->versicherung) * -1;
return number_format((float)$summe, 2, '.', '');
}
return false;
}
else
{
$this->errormsg = 'Fehler bei der Abfrage aufgetreten';
return false;
}
}
}
?>
+17 -34
View File
@@ -388,12 +388,6 @@ html {
#nav-search > .input-group > * {
border-radius: 0 !important;
}
#nav-search .searchbar_results {
top: 100% !important;
left: 0;
right: 0 !important;
width: 100% !important;
}
/* frame */
.in-frame {
@@ -467,7 +461,14 @@ html {
/* overflow: visible !important; */
}
#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 {
position: initial;
@@ -483,12 +484,7 @@ html {
display: none;
}
#nav-logo {
position: fixed;
top: 0;
left: 0;
height: var(--fhc-cis-header-height);
width: var(--fhc-cis-menu-width);
background-color: var(--fhc-primary);
padding: var(--fhc-cis-header-py) var(--fhc-cis-header-px);
z-index: 2;
}
@@ -503,37 +499,27 @@ html {
top: var(--fhc-cis-header-height);
display: flex;
flex-direction: column;
}
#nav-main-sticky > :not(#nav-main-toggle) {
overflow: auto;
height: 100%;
}
#nav-main-toggle {
width: 0;
z-index: 1;
}
#nav-main-toggle .btn,
#nav-main-toggle .fa-arrow-circle-left {
#nav-main-toggle:hover {
background-color: var(--fhc-secondary-highlight);
}
#nav-main-toggle .div,
#nav-main-toggle .fa-chevron-left {
transition: all 0.5s ease-in-out;
}
#nav-main-toggle .collapsed.btn {
background-color: transparent !important;
}
#nav-main-toggle .collapsed .fa-arrow-circle-left {
#nav-main-toggle .collapsed .fa-chevron-left {
transform: scaleX(-1);
color: var(--fhc-black-40);
}
#nav-search {
position: fixed;
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;
position: relative;
}
#nav-user-btn {
border-width: 0;
@@ -568,7 +554,7 @@ html {
#nav-main-menu {
height: 100%;
background-color: var(--fhc-cis-menu-bg);
background-color: var(--fhc-primary);
}
#nav-main-menu > div {
width: var(--fhc-cis-menu-width);
@@ -610,9 +596,6 @@ html {
}
#nav-user{
position: relative;
}
#nav-user-btn {
}
#nav-user-btn img {
object-fit: cover;
-4
View File
@@ -197,10 +197,6 @@ html.fs_huge {
margin-bottom: -1px;
}
.tiny-90 div.tox.tox-tinymce {
height: 90% !important;
}
/* slim begin */
.stv .form-label {
margin-bottom: .15rem;
+6
View File
@@ -35,5 +35,11 @@ export default {
method: 'get',
url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}`
};
},
getSemesterAverageGrade(semester) {
return {
method: 'get',
url: `/api/frontend/v1/Lehre/semesterAverageGrade/${semester}`
}
}
};
+15 -3
View File
@@ -38,6 +38,10 @@ export default {
};
},
insert(params) {
if(params.betrag)
{
params.betrag = params.betrag.replace(',', '.');
}
return {
method: 'post',
url: 'api/frontend/v1/stv/konto/insert',
@@ -52,6 +56,10 @@ export default {
};
},
edit(params) {
if(params.betrag)
{
params.betrag = params.betrag.replace(',', '.');
}
return {
method: 'post',
url: 'api/frontend/v1/stv/konto/update',
@@ -65,10 +73,14 @@ export default {
params: { buchungsnr }
};
},
getBuchungstypen() {
getBuchungstypen(studiensemester_kurzbz) {
let url = 'api/frontend/v1/stv/konto/getBuchungstypen'
if (!!studiensemester_kurzbz)
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
return {
method: 'get',
url: 'api/frontend/v1/stv/konto/getBuchungstypen'
url: url
};
}
},
};
+12 -13
View File
@@ -252,20 +252,12 @@ const app = Vue.createApp({
name: 'CisApp',
data: () => ({
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() {
return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language),
isMobile: this.isMobile,
isMobile: Vue.computed(() => this.windowWidth < 767),
}
},
methods: {
@@ -301,14 +293,21 @@ const app = Vue.createApp({
this.$router.push(route);
}
}
},
handleWindowResize() {
this.windowWidth = window.innerWidth;
},
},
mounted() {
created() {
this.windowWidth = window.innerWidth;
},
async mounted() {
document.addEventListener('click', this.handleClick);
window.addEventListener("resize", this.handleWindowResize);
},
beforeUnmount() {
document.removeEventListener('click', this.handleClick);
window.removeEventListener("resize", this.handleWindowResize);
},
});
+15 -10
View File
@@ -134,26 +134,27 @@ const app = Vue.createApp({
childactions: []
}
}
}
},
windowWidth: 0,
};
},
computed: {
isMobile() {
const smallScreen = window.matchMedia("(max-width: 767px)").matches;
const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
return smallScreen;// && touchCapable;
},
},
provide() {
return {
isMobile: this.isMobile
isNarrow: Vue.computed(() => this.windowWidth < 992),
isMobile: Vue.computed(() => this.windowWidth < 767),
}
},
methods: {
searchfunction: function(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")),
@@ -183,6 +184,10 @@ const app = Vue.createApp({
openOtherLvPlanAction,
);
}
window.addEventListener("resize", this.handleWindowResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleWindowResize);
},
});
@@ -147,12 +147,13 @@ export default {
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
class="d-flex flex-row justify-content-center gap-1 align-items-center"
>
<i
<span
v-for="(subEvent, subEventIndex) in event.events"
:key="subEventIndex"
class="fa-solid fa-circle fa-2xs"
:style="subEvent.farbe ? {color: '#' + subEvent.farbe} : {}"
></i>
: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'"
+3
View File
@@ -194,6 +194,9 @@ export default {
v-else
:is="renderers[event.type]?.calendarEvent"
:event="event"
:timeSlotDisplayBehavior="
$props.mode.toLowerCase() === 'list' ? 'always' : 'default'
"
></component>
</div>
</template>
+1
View File
@@ -97,6 +97,7 @@ export default {
<component
:is="renderers[event.type]?.calendarEvent"
:event="event"
:timeSlotDisplayBehavior="'always'"
></component>
</div>
</template>
+2 -2
View File
@@ -109,7 +109,7 @@ export default {
<h2 ref="newsPageHeading" class="fhc-primary-color">News</h2>
<hr/>
<pagination
v-show="content?true:false"
v-if="content?true:false"
:page="page"
:page_size="page_size"
@pageUpdated="afterPageUpdated($event)"
@@ -127,7 +127,7 @@ export default {
</div>
</div>
<pagination
v-show="content?true:false"
v-if="content?true:false"
:page="page"
:page_size="page_size"
@pageUpdated="afterPageUpdated($event)"
@@ -29,51 +29,51 @@ components:{
},
template:/*html*/`
<div id="fhc-studiengang-informationen">
<template v-if="studiengang?.bezeichnung && semester">
<div class="card card-body mb-3 border-0">
<div class="mb-1">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiengang')}}:</h2>
<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>
<template v-if="studiengang?.bezeichnung && semester">
<div class="card card-body mb-3 border-0">
<div class="mb-1">
<h2 class="h4 mb-1 pb-0">{{$p.t('lehre','studiengang')}}:</h2>
<span class="mb-1">{{studiengang?.bezeichnung}}</span>
</div>
</template>
<template v-else>
<template v-for="person in collection">
<div class="mb-3">
<studiengang-person v-bind="person"></studiengang-person>
<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>
</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 v-if="hochschulvertr && Array.isArray(hochschulvertr) && hochschulvertr.length >0">
<studiengang-vertretung showBezeichnung :title="$p.t('studiengangInformation', 'Hochschulvertretung')" :vertretungsList="hochschulvertr"></studiengang-vertretung>
</template>
<template v-if="stdv && Array.isArray(stdv) && stdv.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Studienvertretung').concat(studiengang.kurzbzlang??'')" :vertretungsList="stdv"></studiengang-vertretung>
</template>
<template v-if="jahrgangsvertr && Array.isArray(jahrgangsvertr) && jahrgangsvertr.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Jahrgangsvertretung')" :vertretungsList="jahrgangsvertr"></studiengang-vertretung>
</template>
<template v-if="hochschulvertr && Array.isArray(hochschulvertr) && hochschulvertr.length >0">
<studiengang-vertretung showBezeichnung :title="$p.t('studiengangInformation', 'Hochschulvertretung')" :vertretungsList="hochschulvertr"></studiengang-vertretung>
</template>
<template v-if="stdv && Array.isArray(stdv) && stdv.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Studienvertretung').concat(studiengang?.kurzbzlang??'')" :vertretungsList="stdv"></studiengang-vertretung>
</template>
<template v-if="jahrgangsvertr && Array.isArray(jahrgangsvertr) && jahrgangsvertr.length >0">
<studiengang-vertretung :title="$p.t('studiengangInformation', 'Jahrgangsvertretung')" :vertretungsList="jahrgangsvertr"></studiengang-vertretung>
</template>
</div>
`,
@@ -27,7 +27,7 @@ export default {
<dl class="stgkontaktinfo">
<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>
<dd class="mb-3">{{ort}}</dd>
@@ -54,6 +54,17 @@ export default {
return this.lv.bezeichnung;
}
},
watch: {
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);
@@ -82,6 +82,17 @@ export default {
];
}
},
watch: {
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);
@@ -137,6 +137,15 @@ export default {
this.$router.go();
},
},
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: {
handleChangeDate(day, newMode) {
+15
View File
@@ -130,6 +130,17 @@ export default {
];
},
},
watch: {
async isMobile() {
await this.$nextTick();
this.handleChangeMode(
this.currentMode,
luxon.DateTime.fromISO(this.currentDay, {
zone: this.timezone,
}),
);
},
},
methods: {
loadLvPlan() {
if (!this.formData.stgkz) {
@@ -308,6 +319,10 @@ export default {
this.loadListGroup();
}
}
if (this.formData.stgkz) {
this.loadLvPlan();
}
},
template: `
<div class="cis-lvplan-stg-org d-flex flex-column h-100">
+87 -57
View File
@@ -30,7 +30,7 @@ export default {
menuOpen:true,
};
},
inject: ["isMobile"],
inject: ["isNarrow", "isMobile"],
provide(){
return{
setActiveEntry: this.setActiveEntry,
@@ -113,71 +113,101 @@ export default {
});
},
template: /*html*/`
<div
id="header-options-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div class="d-flex flex-row align-items-center gap-2 h-100" style="width: 79px">
<button id="nav-main-btn" class="navbar-toggler rounded-0 px-2 border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<span v-if="isMobile" class="d-flex flex-row align-items-center">
<theme-switch></theme-switch>
</span>
<div id="cis-header-bar" class="d-flex flex-row flex-grow-1">
<div id="nav-logo" class="d-none d-lg-block">
<div class="d-flex h-100 justify-content-between">
<a :href="rootUrl">
<img :src="logoUrl" alt="Corporate Identity Logo">
</a>
</div>
</div>
</div>
<fhc-searchbar
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
ref="searchbar"
id="nav-search"
class="fhc-searchbar w-100 py-1 py-lg-2"
></fhc-searchbar>
<div id="nav-logo" class="d-none d-lg-block">
<div class="d-flex h-100 justify-content-between">
<a :href="rootUrl">
<img :src="logoUrl" alt="Corporate Identity Logo">
</a>
<theme-switch></theme-switch>
</div>
</div>
<div
id="header-usermenu-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div
:style="!isMobile ? '' : 'width: 51px'"
id="nav-user"
v-if="isNarrow"
:class="{'collapse multi-collapse collapse-horizontal show': isMobile}"
id="navbar-toggler-collapsible"
>
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
<div class="d-flex flex-row align-items-center h-100" style="width: 35px">
<button id="nav-main-btn" class="navbar-toggler rounded-0 px-2 border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<fhc-searchbar
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
ref="searchbar"
id="nav-search"
class="fhc-searchbar flex-grow-1 py-1 py-lg-2"
>
<template #collapseToggler="{ isSearchShownInMobileView }">
<span
v-if="isMobile"
type="button"
data-bs-toggle="collapse"
data-bs-target=".multi-collapse"
aria-controls="searchbar-collapsible navbar-toggler-collapsible options-collapsible"
aria-expanded="false"
class="d-flex flex-row align-items-center pe-1"
style="color: white"
>
<i v-if="isSearchShownInMobileView" class="fa-solid fa-chevron-left ps-3"></i>
<i v-else class="fa-solid fa-magnifying-glass ps-2"></i>
</span>
</template>
</fhc-searchbar>
<div
id="options-collapsible"
:class="{'collapse multi-collapse collapse-horizontal show': isMobile}"
>
<div :style="!isMobile ? '' : 'width: 105px'" class="d-flex flex-row ps-3 justify-content-end">
<span class="d-flex flex-row align-items-center">
<theme-switch></theme-switch>
</span>
<div id="nav-user">
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
</div>
</div>
</div>
</div>
<nav id="nav-main" class="offcanvas offcanvas-start" tabindex="-1" aria-labelledby="nav-main-btn" data-bs-backdrop="false">
<div id="nav-main-sticky">
<div id="nav-main-toggle" class="position-static d-none d-lg-block ">
<button :aria-label="menuCollapseAriaLabel" type="button" @click="menuOpen = !menuOpen" class="btn text-light rounded-0 p-1 d-flex align-items-center" data-bs-toggle="collapse" data-bs-target=".nav-menu-collapse" aria-expanded="true" aria-controls="nav-sprachen nav-main-menu">
<i aria-hidden="true" class="fa fa-arrow-circle-left fhc-text"></i>
</button>
</div>
<div class="offcanvas-body p-0">
<div id="nav-main-menu" class="nav-menu-collapse collapse collapse-horizontal show">
<div>
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeEntry" v-for="entry in entries" :key="entry.content_id" :entry="entry" />
<div class="d-flex flex-row h-100">
<div class="offcanvas-body p-0">
<div id="nav-main-menu" class="nav-menu-collapse collapse collapse-horizontal show">
<div class="flex-grow-1">
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeEntry" v-for="entry in entries" :key="entry.content_id" :entry="entry" />
</div>
</div>
</div>
<div id="nav-main-toggle" class="d-none d-lg-block">
<div
@click="menuOpen = !menuOpen"
:aria-label="menuCollapseAriaLabel"
type="button"
class="h-100 d-flex align-items-center px-2"
data-bs-toggle="collapse"
data-bs-target=".nav-menu-collapse"
aria-expanded="true"
aria-controls="nav-sprachen nav-main-menu"
>
<i aria-hidden="true" class="fa-solid fa-chevron-left fhc-text"></i>
</div>
</div>
</div>
+3 -2
View File
@@ -36,8 +36,9 @@ export default {
return this.lvs.filter(lv => lv.studiengang_kz == studiengang.studiengang_kz && lv.semester == studiengang.semester);
}
},
template: `<div class="mylv-semester" v-if="ready">
<mylv-semester-studiengang v-for="studiengang in studiengaenge" :key="studiengang.studiengang_kz" v-bind="studiengang" :lvs="lvsForStudiengang(studiengang)"/>
template: `
<div class="mylv-semester" v-if="ready">
<mylv-semester-studiengang v-for="studiengang in studiengaenge" :key="studiengang.studiengang_kz" v-bind="studiengang" :lvs="lvsForStudiengang(studiengang)" :semesterInfo="$props.semester" />
</div>
<div class="mylv-semester text-center" v-else>
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
@@ -14,6 +14,7 @@ export default {
bezeichnung: String,
kuerzel: String,
semester: [String,Number],
semesterInfo: [String,Number],
lvs: Array,
sg_bezeichnung_eng: String
},
@@ -44,7 +45,7 @@ export default {
<mylv-semester-studiengang-lv v-bind="lv" class="text-center h-100"></mylv-semester-studiengang-lv>
</div>
</div>
<mylv-semester-studiengang-average-grade :lvs="lvs" />
<mylv-semester-studiengang-average-grade :semesterInfo="$props.semesterInfo" />
</div>
</div>`
};
@@ -1,50 +1,42 @@
import Phrasen from "../../../../../mixins/Phrasen.js";
import ApiLehre from "../../../../../api/factory/lehre.js";
export default {
mixins: [
Phrasen
],
props: {
lvs: Array,
semesterInfo: String,
},
data: ( ) =>{
data() {
return {
gradeAverage: null,
gradeWeightedAverage: null,
existingGrades: false
}
},
methods: {
calculateAverages(){
let sum = 0;
let count = 0;
let sumWeighted = 0;
let sumEcts = 0;
async fetchAverageGrade() {
this.gradeAverage = null;
this.gradeWeightedAverage = null;
if (!this.$props.semesterInfo) return;
this.lvs.forEach((lv) => {
if ((lv.znote >= 1 && lv.znote <= 5) && lv.znote!= null) {
this.existingGrades = true;
sum+= lv.znote;
count++;
sumWeighted += lv.znote * Number(lv.ects);
sumEcts += Number(lv.ects);
}
});
this.gradeAverage = (sum/count).toFixed(2);
this.gradeWeightedAverage = (sumWeighted/sumEcts).toFixed(2);
}
let gradeAverageResponse = await this.$api.call(
ApiLehre.getSemesterAverageGrade(this.$props.semesterInfo),
);
const gradeAverageResponseData = gradeAverageResponse.data;
this.gradeAverage =
gradeAverageResponseData.average_grade?.toFixed(2);
this.gradeWeightedAverage =
gradeAverageResponseData.weighted_average_grade?.toFixed(2);
},
},
watch: {
lvs: {
handler() {
this.calculateAverages();
},
deep: true,
immediate: true
}
semesterInfo() {
this.fetchAverageGrade();
},
},
mounted(){
this.calculateAverages();
async created() {
await this.fetchAverageGrade();
},
template: /*html*/`
<div class="card mylv-semester-studiengang-grades">
@@ -53,7 +45,7 @@ export default {
<h6>{{$p.t('lehre/notenstatistik')}}</h6>
</div>
<div v-if="existingGrades">
<div v-if="gradeAverage && gradeWeightedAverage">
<table class="card-body table w-auto mx-auto">
<tbody>
<tr>
@@ -79,7 +71,7 @@ export default {
<p>{{$p.t('lehre/info_noGradesYet')}}</p>
</div>
<div v-if="existingGrades" class="card-footer d-flex align-items-start text-muted small">
<div v-if="gradeAverage && gradeWeightedAverage" class="card-footer d-flex align-items-start text-muted small">
<i class="fa fa-circle-info me-2 mt-1"></i>
<div>
<strong>{{$p.t('ui', 'hinweis')}}</strong><br>
@@ -24,9 +24,9 @@ export default {
farbe: String,
lvinfo: Boolean,
benotung: Boolean,
lvnote: String,
lvnote: [String, Number],
lvnotebez: Array,
znote: String,
znote: [String, Number],
znotebez: Array,
studiengang_kuerzel: String,
semester: [String, Number],
@@ -37,6 +37,11 @@ export default {
positiv: Boolean,
note_index: String
},
provide() {
return {
studium_studiensemester: Vue.computed(() => this.studien_semester),
}
},
data: () => {
return {
pruefungenData: null,
@@ -119,7 +124,6 @@ export default {
}
},
openPruefungen() {
// early return if the pruefungenData is empty or not set
if (!this.LvHasPruefungenInformation) return;
LvPruefungen.popup({
@@ -205,21 +209,14 @@ export default {
</template>
</div>
<div v-if="!emptyMenu" class="card-footer">
<div class="row">
<!-- template for the LV if there are multiple pruefungen -->
<template v-if="LvHasPruefungenInformation">
<a href="#" class="col-auto text-start text-decoration-none" @click.prevent="openPruefungen">
<i class="fa fa-check text-success" v-if="positiv"></i>
<span class="ps-1" :style="'color:'+gradeColor">{{ grade || $p.t('lehre/noGrades') }}</span>
</a>
</template>
<!-- template for the LV with no pruefungen -->
<template v-else>
<span class="col-auto text-start text-decoration-none" >
<i class="fa fa-check text-success" v-if="positiv"></i>
<span class="ps-1" :style="'color:'+gradeColor">{{ grade || $p.t('lehre/noGrades') }}</span>
</span>
</template>
<div
@click.prevent="openPruefungen()"
:type="LvHasPruefungenInformation ? 'button' : ''"
class="d-flex flex-row align-items-center gap-1"
>
<i class="fa fa-check text-success" v-if="positiv"></i>
<span :style="'color:'+gradeColor">{{ grade || $p.t('lehre/noGrades') }}</span>
<i v-if="LvHasPruefungenInformation" class="fa fa-circle-info ms-1"></i>
</div>
</div>
</div>`
+26 -25
View File
@@ -96,34 +96,35 @@ export default {
},
template: `
<h2>{{$p.t('lehre/myLV')}}</h2>
<hr>
<div class="mylv-student" v-if="ready">
<div v-if="currentSemester" class="row justify-content-center mb-3">
<div class="col-auto d-none">
<label class="col-form-label">{{$p.t('lehre/studiensemester')}}</label>
</div>
<div class="col-auto">
<div class="input-group">
<button :aria-label="$p.t('lehre','previousStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','previousStudSemester')}" class="btn btn-outline-secondary" type="button" :disabled="currentIsFirst" @click="prevSem">
<i class="fa fa-caret-left" aria-hidden="true"></i>
</button>
<select ref="studiensemester" v-model="currentSemester" class="form-select" :aria-label="$p.t('global/studiensemester_auswaehlen')" @change="updateRouter($event.target.value)">
<option v-for="semester in studiensemester" :key="semester.studiensemester_kurzbz">{{semester.studiensemester_kurzbz}}</option>
</select>
<button class="btn btn-outline-secondary" :aria-label="$p.t('lehre','nextStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','nextStudSemester')}" type="button" :disabled="currentIsLast" @click="nextSem">
<i class="fa fa-caret-right" aria-hidden="true"></i>
</button>
<div>
<h2>{{$p.t('lehre/myLV')}}</h2>
<hr>
<div class="mylv-student" v-if="ready">
<div v-if="currentSemester" class="row justify-content-center mb-3">
<div class="col-auto d-none">
<label class="col-form-label">{{$p.t('lehre/studiensemester')}}</label>
</div>
<div class="col-auto">
<div class="input-group">
<button :aria-label="$p.t('lehre','previousStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','previousStudSemester')}" class="btn btn-outline-secondary" type="button" :disabled="currentIsFirst" @click="prevSem">
<i class="fa fa-caret-left" aria-hidden="true"></i>
</button>
<select ref="studiensemester" v-model="currentSemester" class="form-select" :aria-label="$p.t('global/studiensemester_auswaehlen')" @change="updateRouter($event.target.value)">
<option v-for="semester in studiensemester" :key="semester.studiensemester_kurzbz">{{semester.studiensemester_kurzbz}}</option>
</select>
<button class="btn btn-outline-secondary" :aria-label="$p.t('lehre','nextStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','nextStudSemester')}" type="button" :disabled="currentIsLast" @click="nextSem">
<i class="fa fa-caret-right" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="alert alert-danger" role="alert" v-else>
{{$p.t('lehre/noLvFound')}}
</div>
<mylv-semester v-bind="current"/>
</div>
<div class="alert alert-danger" role="alert" v-else>
{{$p.t('lehre/noLvFound')}}
<div class="mylv-student text-center" v-else>
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
<mylv-semester v-bind="current"/>
</div>
<div class="mylv-student text-center" v-else>
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>`
};
@@ -36,7 +36,7 @@ export default {
return {
showModal: false,
editDataFilter: null,
preloadedPhrasen:{},
arePhrasesPreloaded: false,
// tabulator options
funktionen_table_options: {
persistenceID: "filterTableMaProfilFunktionen",
@@ -48,6 +48,7 @@ export default {
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
responsiveLayoutCollapseStartOpen: false,
columns: [
{
title:
@@ -58,24 +59,27 @@ export default {
formatter: "responsiveCollapse",
maxWidth: 40,
headerClick: this.collapseFunction,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.bezeichnungPhrase),
title: Vue.computed(() => this.$p.t('ui/bezeichnung')),
field: "Bezeichnung",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.organisationseinheitPhrase),
title: Vue.computed(() => this.$p.t('lehre/organisationseinheit')),
field: "Organisationseinheit",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 1,
},
{
title: Vue.computed(() => this.preloadedPhrasen.gueltigVonPhrase),
title: Vue.computed(() => this.$p.t('global/gueltigVon')),
field: "Gültig_von",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -83,10 +87,11 @@ export default {
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 4,
},
{
title: Vue.computed(() => this.preloadedPhrasen.gueltigBisPhrase),
title: Vue.computed(() => this.$p.t('global/gueltigBis')),
field: "Gültig_bis",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
@@ -94,14 +99,16 @@ export default {
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 3,
},
{
title: Vue.computed(() => this.preloadedPhrasen.wochenstundenPhrase),
title: Vue.computed(() => this.$p.t('profil/wochenstunden')),
field: "Wochenstunden",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 2,
},
],
},
@@ -116,6 +123,7 @@ export default {
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
data: [{betriebsmittel: "", Nummer: "", Ausgegeben_am: ""}],
responsiveLayoutCollapseStartOpen: false,
columns: [
{
title:
@@ -126,32 +134,36 @@ export default {
formatter: "responsiveCollapse",
maxWidth: 40,
headerClick: this.collapseFunction,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.entlehnteBetriebsmittelPhrase),
title: Vue.computed(() => this.$p.t('profil/entlehnteBetriebsmittel')),
field: "betriebsmittel",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() => this.preloadedPhrasen.inventarnummerPhrase),
title: Vue.computed(() => this.$p.t('profil/inventarnummer')),
field: "Nummer",
headerFilter: true,
resizable: true,
minWidth: 200,
visible: true
visible: true,
responsive: 2,
},
{
title: Vue.computed(() => this.preloadedPhrasen.ausgabedatumPhrase),
title: Vue.computed(() => this.$p.t('profil/ausgabedatum')),
field: "Ausgegeben_am",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 1,
},
],
}
@@ -166,11 +178,9 @@ export default {
methods: {
betriebsmittelTableBuilt: function () {
this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
this.$refs.betriebsmittelTable.tabulator.setData(this.data.mittel);
},
funktionenTableBuilt: function () {
this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns)
this.$refs.funktionenTable.tabulator.setData(this.data.funktionen);
},
hideEditProfilModal: function () {
@@ -221,8 +231,8 @@ export default {
});
},
setTableColumnTitles() { // reevaluates computed phrasen
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
if(this.$refs.funktionenTable) this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns)
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns);
if(this.$refs.funktionenTable) this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns);
},
datetimeFormatterParams: function() {
const params = {
@@ -308,15 +318,7 @@ export default {
created() {
// preload phrasen
this.$p.loadCategory(["ui","lehre","global","profil"]).then(() => {
this.preloadedPhrasen.bezeichnungPhrase = this.$p.t('ui/bezeichnung');
this.preloadedPhrasen.organisationseinheitPhrase = this.$p.t('lehre/organisationseinheit');
this.preloadedPhrasen.gueltigVonPhrase = this.$p.t('global/gueltigVon');
this.preloadedPhrasen.gueltigBisPhrase = this.$p.t('global/gueltigBis');
this.preloadedPhrasen.wochenstundenPhrase = this.$p.t('profil/wochenstunden');
this.preloadedPhrasen.entlehnteBetriebsmittelPhrase = this.$p.t('profil/entlehnteBetriebsmittel');
this.preloadedPhrasen.inventarnummerPhrase = this.$p.t('profil/inventarnummer');
this.preloadedPhrasen.ausgabedatumPhrase = this.$p.t('profil/ausgabedatum');
this.preloadedPhrasen.loaded=true;
this.arePhrasesPreloaded = true;
});
//? sorts the profil Updates: pending -> accepted -> rejected
this.data.profilUpdates?.sort(this.sortProfilUpdates);
@@ -336,8 +338,8 @@ export default {
<div class="container-fluid text-break fhc-form" >
<edit-profil v-if="showModal" ref="editModal" :isMitarbeiter="true" @hideBsModal="hideEditProfilModal" :value="JSON.parse(JSON.stringify(filteredEditData))" :titel="$p.t('profil','profilBearbeiten')"></edit-profil>
<div class="row">
<div class="d-md-none col-12 ">
<!-- Bearbeiten Button -->
<div class="d-md-none col-12">
<!-- Bearbeiten Button -->
<div v-if="isEditable" class="row mb-3 ">
<div class="col">
<button @click="()=>showEditProfilModal()" type="button" class="text-start card w-100 btn btn-outline-secondary" >
@@ -350,9 +352,9 @@ export default {
</button>
</div>
</div>
<!-- MOBILE PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row mb-3">
<div class="col">
<!-- MOBILE PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates" ></fetch-profil-updates>
</div>
</div>
@@ -365,30 +367,42 @@ export default {
<!-- ROW WITH THE PROFIL INFORMATION -->
<div class="row mb-4">
<div class="col-lg-12 col-xl-6 ">
<!-- PROFIL INFORMATION -->
<div class="row mb-4">
<div class="col">
<!-- PROFIL INFORMATION -->
<profil-information @showEditProfilModal="showEditProfilModal" :title="$p.t('profil','mitarbeiterIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div class="row mb-4 d-md-none">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- MITARBEITER INFO -->
<div class="row mb-4">
<div class=" col-lg-12">
<!-- MITARBEITER INFO -->
<role-information :title="$p.t('profil','mitarbeiterInformation')" :data="roleInformation"></role-information>
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
</div>
<div class="col-xl-6 col-lg-12 ">
<!-- EMAILS -->
<div class="row mb-4">
<div class="col">
<!-- EMAILS -->
<profil-emails :title="this.$p.t('person','email')" :data="data.emails" ></profil-emails>
</div>
</div>
<!-- PRIVATE KONTAKTE-->
<div class="row mb-4 ">
<div class="col">
<!-- PRIVATE KONTAKTE-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -410,9 +424,9 @@ export default {
</div>
</div>
</div>
<!-- PRIVATE ADRESSEN-->
<div class="row mb-4">
<div class="col">
<!-- PRIVATE ADRESSEN-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -437,10 +451,10 @@ export default {
</div>
</div >
<div class="row">
<!-- FUNKTIONEN TABELLE -->
<div class="col-12 mb-4" >
<!-- FUNKTIONEN TABELLE -->
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="funktionenTableBuilt"
:title="$p.t('person','funktionen')"
ref="funktionenTable"
@@ -449,10 +463,10 @@ export default {
:sideMenu="false"
/>
</div>
<!-- BETRIEBSMITTEL TABELLE -->
<div class="col-12 mb-4" >
<!-- BETRIEBSMITTEL TABELLE -->
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="betriebsmittelTableBuilt"
:title="$p.t('profil','entlehnteBetriebsmittel')"
ref="betriebsmittelTable"
@@ -465,11 +479,6 @@ export default {
</div>
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<div v-if="quickLinks.length" class="row mb-4">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- Bearbeiten Button -->
<div class="row d-none d-md-block ">
<div class="col mb-3">
@@ -483,29 +492,36 @@ export default {
</button>
</div>
</div>
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
<div class="col mb-3">
<!-- PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
<div class="row mb-3" >
<div class="col-12">
<!-- AUSWEIS STATUS -->
<ausweis-status :data="data.zutrittsdatum"></ausweis-status>
</div>
</div>
<div class="row mb-3">
<div class="col">
<!-- MAILVERTEILER -->
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
</div>
</div>
<div class="row">
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div class="row mb-3 d-none d-md-block">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
<div class="col mb-3">
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
<!-- AUSWEIS STATUS -->
<div class="row mb-3" >
<div class="col-12">
<ausweis-status :data="data.zutrittsdatum"></ausweis-status>
</div>
</div>
<!-- MAILVERTEILER -->
<div class="row mb-3">
<div class="col">
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
</div>
</div>
</div>
</div>
</div>
@@ -20,7 +20,7 @@ export default {
data() {
return {
collapseIconFunktionen: true,
preloadedPhrasen: {},
arePhrasesPreloaded: false,
funktionen_table_options: {
persistenceID: "filterTableMaViewProfilFunktionen",
persistence: {
@@ -216,19 +216,7 @@ export default {
},
created() {
this.$p.loadCategory(["ui", "lehre", "global", "profil"]).then(() => {
this.preloadedPhrasen.bezeichnungPhrase =
this.$p.t("ui/bezeichnung");
this.preloadedPhrasen.organisationseinheitPhrase = this.$p.t(
"lehre/organisationseinheit",
);
this.preloadedPhrasen.gueltigVonPhrase =
this.$p.t("global/gueltigVon");
this.preloadedPhrasen.gueltigBisPhrase =
this.$p.t("global/gueltigBis");
this.preloadedPhrasen.wochenstundenPhrase = this.$p.t(
"profil/wochenstunden",
);
this.preloadedPhrasen.loaded = true;
this.arePhrasesPreloaded = true;
});
},
@@ -237,15 +225,6 @@ export default {
<div class="container-fluid text-break fhc-form" >
<!-- ROW -->
<div class="row">
<!-- HIDDEN QUICK LINKS -->
<!-- TODO: uncomment when implemented
<div class="d-md-none col-12 ">
<quick-links :title="$p.t('profil','quickLinks')" :mobile="true" ></quick-links>
</div>
-->
<!-- END OF HIDDEN QUCK LINKS -->
<!-- MAIN PANNEL -->
<div class="col-sm-12 col-md-8 col-xxl-9 ">
<!-- ROW WITH PROFIL IMAGE AND INFORMATION -->
@@ -254,27 +233,33 @@ export default {
<div class="row mb-4">
<!-- FIRST KAESTCHEN -->
<div class="col-lg-12 col-xl-6 ">
<!-- Profil Informationen -->
<div class="row mb-4">
<div class="col">
<!-- Profil Informationen -->
<profil-information :title="$p.t('profil','mitarbeiterIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
<!-- END OF PROFIL INFORMATION ROW -->
<!-- INFORMATION CONTENT END -->
</div>
<div class="col-xl-6 col-lg-12 ">
<!-- EMAILS -->
<div class="row mb-4">
<div class="col">
<!-- EMAILS -->
<profil-emails :title="this.$p.t('person','email')" :data="personEmails"></profil-emails>
</div>
</div>
<!-- SECOND ROW OF SECOND COLUMN IN MAIN CONTENT -->
<!-- roleInformation -->
<div class="row mb-4">
<div class=" col-lg-12">
<!-- roleInformation -->
<role-information :data="roleInformation" :title="$p.t('profil','mitarbeiterInformation')"></role-information>
</div>
</div>
@@ -288,7 +273,7 @@ export default {
<div class="row">
<!-- FIRST TABLE -->
<div class="col-12 mb-4" >
<core-filter-cmpt v-if="preloadedPhrasen.loaded" @tableBuilt="funktionenTableBuilt" :title="$p.t('person','funktionen')" ref="funktionenTable" :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" />
<core-filter-cmpt v-if="arePhrasesPreloaded" @tableBuilt="funktionenTableBuilt" :title="$p.t('person','funktionen')" ref="funktionenTable" :tabulator-options="funktionen_table_options" tableOnly :sideMenu="false" />
</div>
<!-- END OF THE ROW WITH THE TABLES UNDER THE PROFIL INFORMATION -->
</div>
@@ -296,15 +281,15 @@ export default {
</div>
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<!-- VISIBLE UNTIL VIEWPORT MD -->
<div v-if="quickLinks.length" class="row mb-4">
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- MAILVERTEILER -->
<div class="row">
<div class="col">
<!-- MAILVERTEILER -->
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
</div>
</div>
@@ -176,6 +176,7 @@ export const Profil = {
this.data = data.profil_data.data;
this.calendarSyncUrls = data.calendar_sync_urls ?? [];
this.authPermissions = data.permissions;
console.log(data.profil_data);
},
zustellAdressenCount() {
if (!this.data || !this.data.adressen) {
@@ -35,7 +35,7 @@ export default {
showModal: false,
collapseIconBetriebsmittel: true,
editDataFilter: null,
preloadedPhrasen:{},
arePhrasesPreloaded: false,
// tabulator options
zutrittsgruppen_table_options: {
persistenceID: "filterTableStudentProfilZutrittsgruppen",
@@ -44,10 +44,12 @@ export default {
},
minHeight: 200,
layout: "fitColumns",
columns: [{
title: Vue.computed(() => this.preloadedPhrasen.zutrittsGruppenPhrase),
columns: [
{
title: Vue.computed(() => this.$p.t('profil/zutrittsGruppen')),
field: "bezeichnung"
}],
}
],
},
betriebsmittel_table_options: {
persistenceID: "filterTableStudentProfilBetriebsmittel",
@@ -59,6 +61,7 @@ export default {
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
responsiveLayoutCollapseFormatter: Vue.$collapseFormatter,
responsiveLayoutCollapseStartOpen: false,
columns: [
{
title:
@@ -69,31 +72,35 @@ export default {
formatter: "responsiveCollapse",
maxWidth: 40,
headerClick: this.collapseFunction,
responsive: 0,
},
{
title: Vue.computed(()=>this.preloadedPhrasen.entlehnteBetriebsmittelPhrase),
title: Vue.computed(()=>this.$p.t('profil/entlehnteBetriebsmittel')),
field: "betriebsmittel",
headerFilter: true,
minWidth: 200,
visible: true
visible: true,
responsive: 0,
},
{
title: Vue.computed(() =>this.preloadedPhrasen.inventarnummerPhrase) ,
title: Vue.computed(() => this.$p.t('profil/inventarnummer')) ,
field: "Nummer",
headerFilter: true,
resizable: true,
minWidth: 200,
visible: true
visible: true,
responsive: 2,
},
{
title: Vue.computed(() =>this.preloadedPhrasen.ausgabedatum) ,
title: Vue.computed(() => this.$p.t('profil/ausgabedatum')) ,
field: "Ausgegeben_am",
headerFilterFunc: 'dates',
headerFilter: dateFilter,
minWidth: 200,
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
formatterParams: this.datetimeFormatterParams(),
responsive: 1,
},
],
},
@@ -113,11 +120,9 @@ export default {
methods: {
betriebsmittelTableBuilt: function () {
this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
this.$refs.betriebsmittelTable.tabulator.setData(this.data.mittel);
},
zutrittsgruppenTableBuilt: function () {
this.$refs.zutrittsgruppenTable.tabulator.setColumns(this.zutrittsgruppen_table_options.columns)
this.$refs.zutrittsgruppenTable.tabulator.setData(
this.data.zuttritsgruppen
);
@@ -225,6 +230,10 @@ export default {
label: `${this.$p.t('person','personenkennzeichen')}`,
value: this.data.personenkennzeichen
},
matrikelnummer: {
label: this.$p.t('person/matrikelnummer'),
value: this.data.matrikelnummer
},
studiengang: {
label: `${this.$p.t('lehre','studiengang')}`,
value: this.data.studiengang
@@ -252,11 +261,7 @@ export default {
created() {
// preload phrasen
this.$p.loadCategory('profil').then(() => {
this.preloadedPhrasen.zutrittsGruppenPhrase = this.$p.t('profil/zutrittsGruppen');
this.preloadedPhrasen.entlehnteBetriebsmittelPhrase = this.$p.t('profil/entlehnteBetriebsmittel');
this.preloadedPhrasen.inventarnummerPhrase = this.$p.t('profil/inventarnummer');
this.preloadedPhrasen.ausgabedatum = this.$p.t('profil/ausgabedatum');
this.preloadedPhrasen.loaded = true;
this.arePhrasesPreloaded = true;
});
//? sorts the profil Updates: pending -> accepted -> rejected
this.data.profilUpdates?.sort(this.sortProfilUpdates);
@@ -287,14 +292,13 @@ export default {
</button>
</div>
</div>
<!-- MOBILE PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row mb-3">
<div class="col">
<!-- MOBILE PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
</div>
<!-- END OF HIDDEN QUCK LINKS -->
<!-- MAIN PANNEL -->
<div class="col-sm-12 col-md-8 col-xxl-9 ">
@@ -303,30 +307,42 @@ export default {
<!-- ROW WITH THE PROFIL INFORMATION -->
<div class="row mb-4 ">
<div class="col-lg-12 col-xl-6 ">
<!-- PROFIL INFORMATION -->
<div class="row mb-4">
<div class="col">
<!-- PROFIL INFORMATION -->
<profil-information @showEditProfilModal="showEditProfilModal" :title="$p.t('profil','studentIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div class="row mb-4 d-md-none">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- STUDENT INFO -->
<div class="row mb-4">
<div class=" col-lg-12">
<!-- STUDENT INFO -->
<role-information :title="$p.t('profil','studentInformation')" :data="roleInformation"></role-information>
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
</div>
<div class="col-xl-6 col-lg-12 ">
<!-- EMAILS -->
<div class="row mb-4">
<div class="col">
<!-- EMAILS -->
<profil-emails :title="this.$p.t('person','email')" :data="data.emails" ></profil-emails>
</div>
</div>
<!-- PRIVATE KONTAKTE-->
<div class="row mb-4 ">
<div class="col">
<!-- PRIVATE KONTAKTE-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -349,9 +365,9 @@ export default {
</div>
</div>
<!-- PRIVATE ADRESSEN-->
<div class="row mb-4">
<div class="col">
<!-- PRIVATE ADRESSEN-->
<div class="card">
<div class="card-header">
<div class="row">
@@ -379,7 +395,7 @@ export default {
<div class="row">
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="betriebsmittelTableBuilt"
:title="$p.t('profil','entlehnteBetriebsmittel')"
ref="betriebsmittelTable"
@@ -389,7 +405,7 @@ export default {
</div>
<div class="col-12 mb-4" >
<core-filter-cmpt
v-if="preloadedPhrasen.loaded"
v-if="arePhrasesPreloaded"
@tableBuilt="zutrittsgruppenTableBuilt"
:title="$p.t('profil','zutrittsGruppen')"
ref="zutrittsgruppenTable"
@@ -403,11 +419,6 @@ export default {
</div>
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<div v-if="quickLinks.length" class="row mb-4">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- Bearbeiten Button -->
<div class="row d-none d-md-block">
<div class="col mb-3">
@@ -421,9 +432,21 @@ export default {
</button>
</div>
</div>
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- CALENDAR SYNC OPTIONS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div class="row mb-3 d-none d-md-block">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- PROFIL UPDATES -->
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
<div class="col mb-3">
<!-- PROFIL UPDATES -->
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
</div>
</div>
@@ -433,18 +456,13 @@ export default {
</div>
</div>
<!-- START OF THE SECOND ROW IN THE SIDE PANEL -->
<!-- MAILVERTEILER -->
<div class="row mb-3">
<div class="col">
<!-- HIER SIND DIE MAILVERTEILER -->
<mailverteiler :title="$p.t('profil','mailverteiler')" :data="data?.mailverteiler"></mailverteiler>
</div>
<!-- END OF THE SECOND ROW IN THE SIDE PANEL -->
</div>
<div class="row">
<div class="col">
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
</div>
</div>
<!-- END OF THE SECOND ROW IN THE SIDE PANEL -->
<!-- END OF SIDE PANEL -->
</div>
<!-- END OF CONTAINER ROW-->
@@ -64,6 +64,10 @@ export default {
label: `${this.$p.t("person", "personenkennzeichen")}`,
value: this.data.personenkennzeichen,
},
matrikelnummer: {
label: this.$p.t('person/matrikelnummer'),
value: this.data.matrikelnummer
},
studiengang: {
label: `${this.$p.t("lehre", "studiengang")}`,
value: this.data.studiengang,
@@ -117,12 +121,12 @@ export default {
<profil-information :data="profilInformation" :title="$p.t('profil','studentIn')" :fotoStatus="fotoStatus"></profil-information>
</div>
</div>
<!-- SECOND ROW OF FIRST COLUMN -->
<div class="row mb-4">
<div class="col">
</div>
</div>
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
</div>
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
<!-- END OF PROFIL INFORMATION ROW -->
<!-- INFORMATION CONTENT END -->
@@ -151,7 +155,8 @@ export default {
<!-- START OF SIDE PANEL -->
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
<!-- START OF THE FIRST ROW IN THE SIDE PANEL -->
<div v-if="quickLinks.length" class="row mb-4">
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
<div class="col">
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
</div>
+23 -22
View File
@@ -71,6 +71,11 @@ export const Raumsuche = {
}
]};
},
computed: {
isDarkMode(){
return this.$theme.theme_name.value == 'dark';
}
},
methods: {
tableResolve(resolve) {
this.tableBuiltResolve = resolve
@@ -176,11 +181,6 @@ export const Raumsuche = {
}
},
computed: {
isDarkMode(){
return this.$theme.theme_name.value == 'dark';
}
},
created() {
this.phrasenPromise = this.$p.loadCategory(['rauminfo', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
@@ -195,27 +195,14 @@ export const Raumsuche = {
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-2">
<VueDatePicker
@contextmenu="(e) => {if (isMobile) {e.preventDefault();}}"
:dark="isDarkMode"
v-model="datum"
:dark="isDarkMode"
:clearable="false"
date-picker
:enable-time-picker="false"
:format="dateFormat"
:text-input="datepickerTextInputOptions"
:min-date="new Date()"
auto-apply>
</VueDatePicker>
</div>
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-1">
<VueDatePicker
@contextmenu="(e) => {if (isMobile) {e.preventDefault();}}"
:dark="isDarkMode"
v-model="von"
:clearable="false"
time-picker
:format="timeFormat"
:text-input="timepickerTextInputOptions"
:is-24="true"
date-picker
auto-apply
>
</VueDatePicker>
@@ -223,13 +210,27 @@ export const Raumsuche = {
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-1">
<VueDatePicker
@contextmenu="(e) => {if (isMobile) {e.preventDefault();}}"
v-model="von"
:dark="isDarkMode"
v-model="bis"
:clearable="false"
time-picker
:format="timeFormat"
:text-input="timepickerTextInputOptions"
:is-24="true"
time-picker
auto-apply
>
</VueDatePicker>
</div>
<div :class="{'pb-1': isMobile}" class="col-12 col-lg-1">
<VueDatePicker
@contextmenu="(e) => {if (isMobile) {e.preventDefault();}}"
v-model="bis"
:dark="isDarkMode"
:clearable="false"
:format="timeFormat"
:text-input="timepickerTextInputOptions"
:is-24="true"
time-picker
auto-apply>
</VueDatePicker>
</div>
@@ -3,6 +3,11 @@ export default {
event: {
type: Object,
required: true
},
timeSlotDisplayBehavior: {
type: String,
default: "default",
// options: default, always, never
}
},
computed:{
@@ -50,7 +55,17 @@ export default {
return luxon.Duration
.fromISOTime(this.event.ende)
.toISOTime({ suppressSeconds: true });
}
},
timeSlotDisplayClasses() {
switch (this.$props.timeSlotDisplayBehavior) {
case "always":
return "d-grid";
case "never":
return "d-none";
default:
return "d-none d-xl-grid";
}
},
},
template: /*html*/`
<div
@@ -58,8 +73,9 @@ export default {
@wheel.stop
>
<div
v-if="!event.allDayEvent && event?.beginn && event?.ende"
class="event-time d-none d-xl-grid h-100"
v-if="!event?.allDayEvent && event?.beginn && event?.ende"
:class="timeSlotDisplayClasses"
class="event-time h-100"
>
<span>{{ start }}</span>
<span>{{ end }}</span>
@@ -3,6 +3,11 @@ export default {
event: {
type: Object,
required: true
},
timeSlotDisplayBehavior: {
type: String,
default: "default",
// options: default, always, never
}
},
computed: {
@@ -50,21 +55,33 @@ export default {
return luxon.Duration
.fromISOTime(this.event.ende)
.toISOTime({ suppressSeconds: true });
}
},
timeSlotDisplayClasses() {
switch (this.$props.timeSlotDisplayBehavior) {
case "always":
return "d-grid";
case "never":
return "d-none";
default:
return "d-none d-xl-grid";
}
},
},
template: /* html */`
<div
class="cis-renderer-reservierungen-calendar-event calendar-event-default h-100 w-100 p-1"
>
<div
v-if="!event.allDayEvent && event?.beginn && event?.ende"
class="event-time d-grid h-100"
v-if="!event?.allDayEvent && event?.beginn && event?.ende"
:class="timeSlotDisplayClasses"
class="event-time h-100"
>
<span>{{ start }}</span>
<span>{{ end }}</span>
</div>
<div class="event-text" v-tooltip="tooltipString">
<span class="event-topic">{{ event.topic }}</span>
<span class="event-place">{{ event.ort_kurzbz }}</span>
<span
v-for="lektor in event.lektor.slice(0, 3)"
class="event-lectors"
@@ -77,7 +94,6 @@ export default {
>
... +{{ event.lektor.length - 3 }}
</span>
<span class="event-place">{{ event.ort_kurzbz }}</span>
</div>
</div>
`,
@@ -63,7 +63,7 @@ export default {
const vm = this;
tinymce.init({
target: this.$refs.editor.$refs.input, //Important: not selector: to enable multiple import of component
//height: 800,
min_height: 300,
//plugins: ['lists'],
toolbar: 'styleselect | bold italic underline | alignleft aligncenter alignright alignjustify | link',
plugins: 'link',
@@ -313,7 +313,7 @@ export default {
<div class="row">
<div class="col-sm-8">
<form-form class="row g-3 mt-2 h-100" ref="formMessage">
<form-form class="row g-3 mt-2 align-content-start" ref="formMessage">
<div class="row mb-3">
@@ -338,7 +338,7 @@ export default {
</div>
<!--Tiny MCE-->
<div class="row mb-3 h-100 tiny-90">
<div class="row mb-3 tiny-90">
<form-input
ref="editor"
:label="$p.t('global','nachricht') + ' *'"
@@ -62,7 +62,7 @@ export default {
const vm = this;
tinymce.init({
target: this.$refs.editor.$refs.input, //Important: not selector: to enable multiple import of component
//height: 800,
min_height: 300,
//plugins: ['lists'],
toolbar: 'styleselect | bold italic underline | alignleft aligncenter alignright alignjustify | link',
plugins: 'link',
@@ -30,6 +30,7 @@ export default {
personId: null,
layoutColumnsOnNewData: false,
height: '400',
arePhrasesLoaded: false
}
},
methods: {
@@ -195,7 +196,7 @@ export default {
],
formatter: (cell, formatterParams) => {
const key = formatterParams[cell.getValue()];
return this.$p.t('messages', key);
return this.$p?.t?.('messages', key) || key;
},
},
{
@@ -305,8 +306,6 @@ export default {
{
event: 'tableBuilt',
handler: async() => {
await this.$p.loadCategory(['global', 'person', 'stv', 'messages', 'ui', 'notiz']);
const setHeader = (field, text) => {
const col = this.$refs.table.tabulator.getColumn(field);
if (!col) return;
@@ -357,6 +356,12 @@ export default {
});*/
},
created(){
this.$p
.loadCategory(['global', 'person', 'stv', 'messages', 'ui', 'notiz'])
.then(() => {
this.arePhrasesLoaded = true;
});
if(this.typeId != 'person_id' && Array.isArray(this.id) && this.id.length === 1) {
const params = {
id: this.id,
@@ -381,6 +386,7 @@ export default {
<!--table-->
<div class="col-sm-6 pt-1">
<core-filter-cmpt
v-if="arePhrasesLoaded"
ref="table"
:tabulator-options="tabulatorOptions"
:tabulator-events="tabulatorEvents"
@@ -413,6 +419,7 @@ export default {
<div class="col-sm-12 pt-6">
<core-filter-cmpt
ref="table"
v-if="arePhrasesLoaded"
:tabulator-options="tabulatorOptions"
:tabulator-events="tabulatorEvents"
table-only
@@ -13,7 +13,7 @@ export default {
required: true,
},
page: {
type: Number | null,
type: [Number, null],
default: null,
},
},
@@ -38,9 +38,9 @@ export default {
<!-- Desktop -->
<div class="d-none d-md-block">
<paginator
v-model:rows="page_size"
v-model:first="first"
@page="afterPageUpdated($event)"
v-model:first="first"
:rows="page_size"
:totalRecords="maxPageCount"
:rowsPerPageOptions="rowsPerPageOptions"
></paginator>
@@ -48,9 +48,9 @@ export default {
<!-- Mobile -->
<div class="d-block d-md-none">
<paginator
v-model:rows="page_size"
v-model:first="first"
@page="afterPageUpdated($event)"
v-model:first="first"
:rows="page_size"
:totalRecords="maxPageCount"
:rowsPerPageOptions="rowsPerPageOptions"
template="FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink RowsPerPageDropdown"
@@ -83,6 +83,8 @@ export default {
});
},
open() {
this.getBuchungstypen(this.currentSemester);
this.data = {
buchungstyp_kurzbz: '',
betrag: '-0.00',
@@ -105,7 +107,7 @@ export default {
const text = typ.standardtext || '';
const creditpoints = typ.credit_points || '';
if (!this.data.betrag || this.data.betrag == '-0.00')
if (!this.data.betrag || this.data.betrag == '-0.00' || this.data.betrag !== amount)
this.data.betrag = amount;
if (!this.data.buchungstext)
@@ -113,7 +115,18 @@ export default {
if (this.config.showCreditpoints && (this.data.credit_points == '0.00' || this.data.credit_points === null))
this.data.credit_points = creditpoints;
}
},
getBuchungstypen(studiensemester_kurzbz)
{
this.$api
.call(ApiKonto.getBuchungstypen(studiensemester_kurzbz))
.then(result => {
this.lists.buchungstypen = result.data;
if (this.data.buchungstyp_kurzbz)
this.checkDefaultBetrag(this.data.buchungstyp_kurzbz);
})
.catch(this.$fhcAlert.handleSystemError);
},
},
template: `
<core-form ref="form" class="stv-details-konto-edit" @submit.prevent="save">
@@ -166,6 +179,7 @@ export default {
<form-input
type="select"
v-model="data.studiensemester_kurzbz"
@change="getBuchungstypen(data.studiensemester_kurzbz)"
name="studiensemester_kurzbz"
:label="$p.t('lehre/studiensemester')"
>
+13 -13
View File
@@ -41,8 +41,8 @@ export default {
),
ajaxResponse: (url, params, response) => response.data,
columns: [
{title: "Typ", field: "type"},
{title: "Betrag", field: "betrag",
{title: "Typ", field: "type", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
{title: "Betrag", field: "betrag", headerFilter: true,
formatter: function(cell) {
let value = cell.getValue();
if (value == null) {
@@ -51,14 +51,14 @@ export default {
return parseFloat(value).toFixed(2);
}
},
{title: "Bezeichnung", field: "bezeichnung"},
{title: "Studiensemester", field: "studiensemester_kurzbz"},
{title: "Pruefung_id", field: "pruefung_id", visible: false},
{title: "mitarbeiter_uid", field: "mitarbeiter_uid", visible: false},
{title: "projektarbeit_id", field: "projektarbeit_id", visible: false},
{title: "lehreinheit_id", field: "lehreinheit_id", visible: true},
{title: "betreuerart_kurzbz", field: "betreuerart_kurzbz", visible: false},
{title: "vertrag_id", field: "vertrag_id", visible: false}, //just for testing
{title: "Bezeichnung", field: "bezeichnung", headerFilter: true},
{title: "Studiensemester", field: "studiensemester_kurzbz", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
{title: "Pruefung_id", field: "pruefung_id", visible: false, headerFilter: true},
{title: "mitarbeiter_uid", field: "mitarbeiter_uid", visible: false, headerFilter: true},
{title: "projektarbeit_id", field: "projektarbeit_id", visible: false, headerFilter: true},
{title: "lehreinheit_id", field: "lehreinheit_id", visible: true, headerFilter: true},
{title: "betreuerart_kurzbz", field: "betreuerart_kurzbz", visible: false, headerFilter: true},
{title: "vertrag_id", field: "vertrag_id", visible: false, headerFilter: true}, //just for testing
{
title: 'Aktionen', field: 'actions',
minWidth: 50,
@@ -110,10 +110,10 @@ export default {
],
layout: 'fitColumns',
layoutColumnsOnNewData: false,
height: '200',
height: '250',
selectableRowsRangeMode: 'click',
selectableRows: true,
persistenceID: 'core-contracts-details-2026021701'
persistenceID: 'core-contracts-details-2026050501'
},
tabulatorEvents: [
{
@@ -137,7 +137,7 @@ export default {
setHeader('type', this.$p.t('global', 'typ'));
setHeader('bezeichnung', this.$p.t('ui', 'bezeichnung'));
setHeader('lehreinheit_id', this.$p.t('ui', 'lehreinheit_id'));
setHeader('lehreinheit_id', this.$p.t('lehre', 'lehreinheit_id'));
setHeader('betrag', this.$p.t('ui', 'betrag'));
setHeader('studiensemester_kurzbz', this.$p.t('lehre', 'studiensemester'));
setHeader('mitarbeiter_uid', this.$p.t('ui', 'mitarbeiter_uid'));
+11 -8
View File
@@ -47,12 +47,13 @@ export default {
this.endpoint.getStatiOfContract(this.person_id, this.vertrag_id)
),
ajaxResponse: (url, params, response) => response.data,
persistenceID: 'core-contracts-status-2026021701',
persistenceID: 'core-contracts-status-2026050501',
columns: [
{title: "Status", field: "bezeichnung"},
{title: "Status", field: "bezeichnung", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
{
title: "Datum",
field: "datum",
headerFilter: true,
formatter: function (cell) {
const dateStr = cell.getValue();
const date = new Date(dateStr); // Convert to Date object
@@ -66,14 +67,15 @@ export default {
});
}
},
{title: "vertrag_id", field: "vertrag_id", visible: false},
{title: "Vertragsstatus", field: "vertragsstatus_kurzbz", visible: false},
{title: "User", field: "mitarbeiter_uid", visible: false},
{title: "insertvon", field: "insertvon", visible: false},
{title: "vertrag_id", field: "vertrag_id", visible: false, headerFilter: true},
{title: "Vertragsstatus", field: "vertragsstatus_kurzbz", visible: false, headerFilter: true},
{title: "User", field: "mitarbeiter_uid", visible: false, headerFilter: true},
{title: "insertvon", field: "insertvon", visible: false, headerFilter: true},
{
title: "insertamum",
field: "insertamum",
visible: false,
headerFilter: true,
formatter: function (cell) {
const dateStr = cell.getValue();
const date = new Date(dateStr);
@@ -87,11 +89,12 @@ export default {
});
}
},
{title: "updatevon", field: "updatevon", visible: false},
{title: "updatevon", field: "updatevon", visible: false, headerFilter: true},
{
title: "updateamum",
field: "updateamum",
visible: false,
headerFilter: true,
formatter: function (cell) {
const dateStr = cell.getValue();
const date = new Date(dateStr);
@@ -148,7 +151,7 @@ export default {
],
layout: 'fitColumns',
layoutColumnsOnNewData: false,
height: '200',
height: '250',
selectableRowsRangeMode: 'click',
selectableRows: true,
},
@@ -30,10 +30,11 @@ export default {
),
ajaxResponse: (url, params, response) => response.data,
columns: [
{title: "Typ", field: "type", width: 100},
{title: "Typ", field: "type", width: 100, headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
{
title: "Betrag",
field: "betrag1",
headerFilter: true,
formatter: function(cell) {
let value = cell.getValue();
if (value == null) {
@@ -41,28 +42,29 @@ export default {
}
return parseFloat(value).toFixed(2);
}},
{title: "Bezeichnung", field: "bezeichnung", width: 150},
{title: "Studiensemester", field: "studiensemester_kurzbz", width: 160},
{title: "mitarbeiter_uid", field: "mitarbeiter_uid", visible: false},
{title: "projektarbeit_id", field: "projektarbeit_id", visible: false},
{title: "lehreinheit_id", field: "lehreinheit_id", visible: true},
{title: "betreuerart_kurzbz", field: "betreuerart_kurzbz", visible: false},
{title: "Vertragsstunden", field: "vertragsstunden", visible: false},
{title: "vertrag_id", field: "vertrag_id", visible: false}, //just for testing
{title: "Bezeichnung", field: "bezeichnung", width: 150, headerFilter: true},
{title: "Studiensemester", field: "studiensemester_kurzbz", width: 160, headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
{title: "mitarbeiter_uid", field: "mitarbeiter_uid", visible: false, headerFilter: true},
{title: "projektarbeit_id", field: "projektarbeit_id", visible: false, headerFilter: true},
{title: "lehreinheit_id", field: "lehreinheit_id", visible: true, headerFilter: true},
{title: "betreuerart_kurzbz", field: "betreuerart_kurzbz", visible: false, headerFilter: true},
{title: "Vertragsstunden", field: "vertragsstunden", visible: false, headerFilter: true},
{title: "vertrag_id", field: "vertrag_id", visible: false, headerFilter: true}, //just for testing
{
title: "VertragsstundenStudiensemester",
field: "vertragsstunden_studiensemester_kurzbz",
visible: false
visible: false,
headerFilter: true
},
],
layout: 'fitColumns',
layoutColumnsOnNewData: false,
height: 150,
height: 250,
selectableRowsRangeMode: 'click',
selectableRows: true,
selectableRowsRollingSelection: false, //only allow multiselect with STRG
index: "lehreinheit_id",
persistenceID: 'core-contracts-unassigned-2026021701'
persistenceID: 'core-contracts-unassigned-2026050501'
},
tabulatorEvents: [
{
@@ -100,7 +102,7 @@ export default {
setHeader('type', this.$p.t('global', 'typ'));
setHeader('bezeichnung', this.$p.t('ui', 'bezeichnung'));
setHeader('lehreinheit_id', this.$p.t('ui', 'lehreinheit_id'));
setHeader('lehreinheit_id', this.$p.t('lehre', 'lehreinheit_id'));
setHeader('betrag1', this.$p.t('ui', 'betrag'));
setHeader('studiensemester_kurzbz', this.$p.t('lehre', 'studiensemester'));
setHeader('mitarbeiter_uid', this.$p.t('ui', 'mitarbeiter_uid'));
+26 -137
View File
@@ -20,9 +20,6 @@ export default {
ContractStati
},
inject: {
/* cisRoot: {
from: 'cisRoot'
},*/
hasSchreibrechte: {
from: 'hasSchreibrechte',
default: false
@@ -54,9 +51,9 @@ export default {
),
ajaxResponse: (url, params, response) => response.data,
columns: [
{title: "Bezeichnung", field: "bezeichnung", width: 300},
{title: "Bezeichnung", field: "bezeichnung", width: 300, headerFilter: true},
{
title: "Betrag", field: "betrag", width: 100,
title: "Betrag", field: "betrag", width: 100, headerFilter: true,
formatter: function (cell) {
let value = cell.getValue();
@@ -66,12 +63,13 @@ export default {
return parseFloat(value).toFixed(2);
}
},
{title: "Vertragstyp", field: "vertragstyp_bezeichnung", width: 125},
{title: "Status", field: "status", width: 100},
{title: "Vertragstyp", field: "vertragstyp_bezeichnung", width: 125, headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
{title: "Status", field: "status", width: 100, headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
{
title: "Vertragsdatum",
field: "vertragsdatum",
width: 128,
headerFilter: true,
formatter: function (cell) {
const dateStr = cell.getValue();
const date = new Date(dateStr);
@@ -82,11 +80,11 @@ export default {
});
}
},
{title: "VertragId", field: "vertrag_id", visible: false},
{title: "Vertragsstunden", field: "vertragsstunden", visible: false},
{title: "VertragsstundenStudiensemester", field: "vertragsstunden_studiensemester_kurzbz", visible: false},
{title: "Anmerkung", field: "anmerkung", visible: false},
{title: "isAbgerechnet", field: "isabgerechnet", visible: false},
{title: "VertragId", field: "vertrag_id", visible: false, headerFilter: true},
{title: "Vertragsstunden", field: "vertragsstunden", visible: false, headerFilter: true},
{title: "VertragsstundenStudiensemester", field: "vertragsstunden_studiensemester_kurzbz", visible: false, headerFilter: true},
{title: "Anmerkung", field: "anmerkung", visible: false, headerFilter: true},
{title: "isAbgerechnet", field: "isabgerechnet", visible: false, headerFilter: true},
{
title: 'Aktionen', field: 'actions',
minWidth: 150,
@@ -140,11 +138,13 @@ export default {
columns: true,
filter: false //to avoids js errors
},
persistenceID: 'core-contracts-2026021701',
persistenceID: 'core-contracts-2026050501',
};
return options;
},
tabulatorEvents() {
const vm = this;
const events = [
{
event: 'tableBuilt',
@@ -177,28 +177,11 @@ export default {
setHeader('actions', this.$p.t('global', 'aktionen'));
}
},
/* {
//is just enabled for ADDON Injection KU: MultiprintHonorarvertrag
//(maybe enable also for ADDON FH Burgenland: MultiAccept later)
event: 'rowClick',
handler: (e, row) => {
if (this.dataPrintHonorar != null && this.dataPrintHonorar.multiselect != null) {
const selectedContract = row.getData().vertrag_id;
const status = row.getData().status;
const bezeichnung = row.getData().bezeichnung;
this.toggleRowClick(selectedContract, status, bezeichnung);
}
}
},*/
{
event: 'rowClick',
handler: (e, row) => {
if (!this.dataPrintHonorar?.multiselect) return;
handler: function (e, row) {
const { vertrag_id, status, bezeichnung, vertragstyp_bezeichnung } = row.getData();
this.toggleRowClick(e, vertrag_id, status, bezeichnung, vertragstyp_bezeichnung);
vm.toggleRowClick(e, vertrag_id, status, bezeichnung, vertragstyp_bezeichnung);
}
},
{
@@ -242,8 +225,6 @@ export default {
person_id() {
this.$refs.table.reloadTable();
this.arraySelectedContracts = [];
/* if(this.dataPrintHonorar?.multiselect)
this.dataPrintHonorar.multiselect = [];*/
},
},
methods: {
@@ -270,7 +251,6 @@ export default {
)
.then(result => {
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete'));
//window.scrollTo(0, 0);
this.reload();
this.contractSelected.vertrag_id = null;
})
@@ -518,19 +498,9 @@ export default {
'content/pdfExport.php?xml=' + this.dataPrintHonorar.xml + '&xsl=' + this.dataPrintHonorar.xsl + '&mitarbeiter_uid=' + this.mitarbeiter_uid + vertragString + '&output=pdf&uid=' + this.mitarbeiter_uid;
window.open(linkToPdf, '_blank');
},
/* toggleRowClick(contractId, status, bezeichnung) {
const index = this.arraySelectedContracts.findIndex(
([id]) => id === contractId
);
if (index !== -1) {
this.arraySelectedContracts.splice(index, 1);
} else {
this.arraySelectedContracts.push([contractId, status, bezeichnung]);
}
},*/
toggleRowClick(event, vertrag_id, status, bezeichnung, vertragstyp_bezeichnung) {
if (!this.dataPrintHonorar?.multiselect) return;
const isMulti = this.dataPrintHonorar?.multiselect === true;
const isCtrl = event.ctrlKey || event.metaKey;
const entry = {
@@ -540,28 +510,29 @@ export default {
vertragstyp_bezeichnung
};
// Single click
if (!isCtrl) {
// allow MultiSelect just in case event multiActionPrintHonorarvertrag
const allowMultiClick = isMulti && isCtrl;
if (!allowMultiClick) {
this.arraySelectedContracts = [entry];
//just mark last selected row as selected
this.$refs.table.tabulator.deselectRow();
this.$refs.table.tabulator.selectRow(vertrag_id);
return;
}
// CTRL / CMD → toggle
const index = this.arraySelectedContracts.findIndex(
e => e.vertrag_id === vertrag_id
);
if (index === -1) {
this.arraySelectedContracts.push(entry);
//this.arraySelectedContracts.push([entry.vertrag_id, entry.status, entry.bezeichnung, entry.vertragstyp_bezeichnung]);
} else {
this.arraySelectedContracts.splice(index, 1);
}
},
/* clearSelection(){
this.arraySelectedContracts = [];
this.$refs.table.tabulator.deselectRow();
}*/
}
},
created() {
Promise.all([
@@ -587,88 +558,6 @@ export default {
});
this.getFormattedDate();
},
/*
TODO(Manu) delete after check
<div class="row mb-3">
<form-input
type="DatePicker"
:label="$p.t('vertrag/datum_vertrag')"
name="vertragsdatum"
v-model="formData.vertragsdatum"
auto-apply
:enable-time-picker="false"
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="text"
:label="$p.t('ui/bezeichnung')"
name="bezeichnung"
v-model="formData.bezeichnung"
>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="select"
:label="$p.t('global/typ')"
v-model="formData.vertragstyp_kurzbz"
name="vertragstyp_kurzbz"
>
<option :value="null">-- {{$p.t('fehlermonitoring', 'keineAuswahl')}} --</option>
<option
v-for="entry in listContractTypes"
:key="entry.vertragstyp_kurzbz"
:value="entry.vertragstyp_kurzbz"
>
{{entry.bezeichnung}}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
:label="$p.t('ui/betrag')"
name="betrag"
v-model="formData.betrag"
>
</form-input>
</div>
<div class="row mb-3" v-if="!statusNew">
<form-input
type="text"
:label="$p.t('ui/stunden') + ' (' + $p.t('vertrag/vertrag_urfassung')+ ')'"
name="vertragsstunden"
v-model="formData.vertragsstunden"
disabled
>
</form-input>
</div>
<div class="row mb-3" v-if="!statusNew">
<form-input
type="text"
:label="$p.t('lehre/studiensemester') + ' (' + $p.t('vertrag/vertrag_urfassung')+ ')'"
name="vertragsstunden_studiensemester_kurzbz"
v-model="formData.vertragsstunden_studiensemester_kurzbz"
disabled
>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="textarea"
:label="$p.t('global/anmerkung')"
name="anmerkung"
v-model="formData.anmerkung"
>
</form-input>
</div>
*/
template: `
<div class="core-contracts h-100 d-flex flex-column">
@@ -25,7 +25,7 @@ export default {
return new Set(this.res.email);
}
},
template: `
template: /*html*/ `
<template-frame
class="searchbar-result-student"
:res="res"
@@ -48,10 +48,16 @@ export default {
{{ res.person_id }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('abschlusspruefung/personenkennzeichen') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.personenkennzeichen }}
</div>
</div>
<div class="searchbar_tablerow">
<div class="searchbar_tablecell searchbar_label">{{ $p.t('person/matrikelnummer') }}</div>
<div class="searchbar_tablecell searchbar_value">
{{ res.matrikelnr }}
{{ res.matrikelnummer }}
</div>
</div>
<div class="searchbar_tablerow">
+26 -15
View File
@@ -94,22 +94,25 @@ export default {
@focusin="searchfocusin"
@focusout="searchfocusout"
>
<span
v-if="isMobile"
type="button"
data-bs-toggle="collapse"
data-bs-target=".multi-collapse"
aria-controls="header-searchbar-collapsible header-options-collapsible header-usermenu-collapsible"
aria-expanded="false"
class="d-flex flex-row align-items-center ps-3 pe-1"
>
<i v-if="isSearchShownInMobileView" class="fa-solid fa-chevron-left"></i>
<i v-else class="fa-solid fa-magnifying-glass"></i>
</span>
<slot name="collapseToggler" :isSearchShownInMobileView="isSearchShownInMobileView">
<span
v-if="isMobile"
type="button"
data-bs-toggle="collapse"
data-bs-target="#searchbar-collapsible"
aria-controls="searchbar-collapsible"
aria-expanded="false"
class="d-flex flex-row align-items-center pe-1"
style="color: white"
>
<i v-if="isSearchShownInMobileView" class="fa-solid fa-chevron-left ps-3"></i>
<i v-else class="fa-solid fa-magnifying-glass ps-2"></i>
</span>
</slot>
<div
:class="{'flex-grow-1': !isMobile, 'collapse multi-collapse collapse-horizontal': isMobile}"
id="header-searchbar-collapsible"
id="searchbar-collapsible"
@[\`show.bs.collapse\`]="isSearchShownInMobileView = true"
@[\`hidden.bs.collapse\`]="isSearchShownInMobileView = false"
>
@@ -117,7 +120,7 @@ export default {
:class="{open: showresult, closed: showresult, 'px-3': isMobile}"
ref="searchbox"
class="h-100 input-group me-2 searchbar_searchbox"
:style="isMobile ? 'width: ' + getMaxWidthOfSearchbarInMobileView() : ''"
:style="isMobile ? 'width: ' + getMaxWidthOfSearchbarInNarrowView() : ''"
>
<span class="input-group-text">
<i class="fa-solid fa-magnifying-glass color-white"></i>
@@ -242,6 +245,11 @@ export default {
}
this.search();
},
isMobile() {
if (!this.isMobile) {
this.isSearchShownInMobileView = false;
}
},
},
mounted() {
this.settingsDropdown = new bootstrap.Collapse(this.$refs.settings, {
@@ -364,6 +372,9 @@ export default {
}
if (this.abortController) this.abortController.abort();
if (!this.searchsettings.searchstr?.length) return;
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, {
@@ -500,7 +511,7 @@ export default {
}
return this.searchoptions.actions[res.type];
},
getMaxWidthOfSearchbarInMobileView() {
getMaxWidthOfSearchbarInNarrowView() {
// body width - hardcoded chevron width; necessary for accurate collapse transition transition
return (
document.querySelector("body").getBoundingClientRect().width -
@@ -43,15 +43,17 @@ if($result = @$db->db_query("SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABL
AND TABLE_NAME='tbl_bookmark' AND COLUMN_NAME = 'tag'
AND DATA_TYPE='character varying' AND character_maximum_length='255';"))
{
$qry = "
ALTER TABLE dashboard.tbl_bookmark
ALTER COLUMN tag TYPE jsonb
USING tag::jsonb;
";
if($db->db_num_rows($result) > 0)
{
$qry = "
ALTER TABLE dashboard.tbl_bookmark
ALTER COLUMN tag TYPE jsonb
USING tag::jsonb;
";
if (!$db->db_query($qry))
echo '<strong>dashboard.tbl_bookmark ' . $db->db_last_error() . '</strong><br>';
else
echo '<br>Tabelle dashboard.tbl_bookmark: Spalte tag auf Typ JSONB geändert';
}
if (!$db->db_query($qry))
echo '<strong>dashboard.tbl_bookmark ' . $db->db_last_error() . '</strong><br>';
else
echo '<br>Tabelle dashboard.tbl_bookmark: Spalte tag auf Typ JSONB geändert';
}
}
+2 -2
View File
@@ -264,8 +264,8 @@ CREATE TABLE IF NOT EXISTS hr.tbl_vertragsbestandteil_lohnguide (
stellenbezeichnung varchar(255),
fachrichtung_kurzbz character varying(32) NOT NULL,
modellstelle_kurzbz character varying(32) NOT NULL,
kommentar_person varchar(255),
kommentar_modellstelle varchar(255),
kommentar_person text,
kommentar_modellstelle text,
CONSTRAINT tbl_vertragsbestandteil_lohnguide_pk PRIMARY KEY (vertragsbestandteil_id),
CONSTRAINT tbl_vertragsbestandteil_fk FOREIGN KEY (vertragsbestandteil_id) REFERENCES hr.tbl_vertragsbestandteil (vertragsbestandteil_id) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT tbl_vertragsbestandteil_lohnguide_fachrichtung_fk FOREIGN KEY (fachrichtung_kurzbz) REFERENCES hr.tbl_lohnguide_fachrichtung (fachrichtung_kurzbz) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE,
+1 -1
View File
@@ -42204,7 +42204,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4fehlerAktualitaetProjektarbeit ',
'phrase' => 'c4fehlerAktualitaetProjektarbeit',
'insertvon' => 'system',
'phrases' => array(
array(