Merge remote-tracking branch 'origin/master'

This commit is contained in:
kindlm
2026-03-26 11:43:59 +01:00
54 changed files with 2070 additions and 1080 deletions
+8 -1
View File
@@ -208,7 +208,14 @@ $config['navigation_header'] = array(
'expand' => true, 'expand' => true,
'sort' => 30, 'sort' => 30,
'requiredPermissions' => 'lehre/anrechnungszeitfenster:rw' 'requiredPermissions' => 'lehre/anrechnungszeitfenster:rw'
) ),
'dashboardadmin' => array(
'link' => site_url('dashboard/Admin'),
'description' => 'Dashboard Admin',
'expand' => true,
'sort' => 40,
'requiredPermissions' => 'dashboard/admin:r'
)
) )
) )
) )
@@ -511,10 +511,11 @@ class Abgabe extends FHCAPI_Controller
return $projektarbeit->projektarbeit_id; return $projektarbeit->projektarbeit_id;
}; };
$projektarbeiten_ids = array_map($mapFunc, $projektarbeiten->retval); $projektarbeiten_ids = array_map($mapFunc, $projektarbeiten->retval);
$ret = $this->ProjektarbeitModel->getProjektarbeitenAbgabetermine($projektarbeiten_ids);
$projektabgaben = $this->getDataOrTerminateWithError($ret, 'general');
if(count($projektarbeiten_ids) > 0) {
$ret = $this->ProjektarbeitModel->getProjektarbeitenAbgabetermine($projektarbeiten_ids);
$projektabgaben = $this->getDataOrTerminateWithError($ret, 'general');
}
forEach($projektarbeiten->retval as $pa) { forEach($projektarbeiten->retval as $pa) {
@@ -846,9 +847,10 @@ class Abgabe extends FHCAPI_Controller
private function getProjektbetreuerEmailByProjektarbeitID($projektarbeit_id) { private function getProjektbetreuerEmailByProjektarbeitID($projektarbeit_id) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id); $result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id);
$email = $this->getDataOrTerminateWithError($result, 'general'); if(count($result->retval) > 0) {
$email = getData($result);
return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email; return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email;
} else return '';
} }
@@ -208,7 +208,6 @@ class Documents extends FHCAPI_Controller
$this->load->model('system/Vorlage_model', 'VorlageModel'); $this->load->model('system/Vorlage_model', 'VorlageModel');
$result = $this->VorlageModel->load($xsl); $result = $this->VorlageModel->load($xsl);
$this->addMeta("ress", $result);
$vorlage = current($this->getDataOrTerminateWithError($result)); $vorlage = current($this->getDataOrTerminateWithError($result));
if (!$vorlage) if (!$vorlage)
show_404(); show_404();
@@ -221,7 +220,7 @@ class Documents extends FHCAPI_Controller
'gedruckt' => true, 'gedruckt' => true,
'insertamum' => date('c'), 'insertamum' => date('c'),
'insertvon' => getAuthUID(), 'insertvon' => getAuthUID(),
'uid' => $this->input->post_get('uid') ?: '', 'uid' => $this->input->post_get('uid') ?: null,
'archiv' => true, 'archiv' => true,
'signiert' => !!$sign_user, 'signiert' => !!$sign_user,
'stud_selfservice' => $vorlage->stud_selfservice 'stud_selfservice' => $vorlage->stud_selfservice
@@ -251,6 +250,9 @@ class Documents extends FHCAPI_Controller
'studiensemester_kurzbz' => $ss, 'studiensemester_kurzbz' => $ss,
'student_uid' => $akteData['uid'] 'student_uid' => $akteData['uid']
]); ]);
if (!hasData($result)) $this->terminateWithError($this->p->t("stv", "error_noLehrverbandAssigned"));
$res = current($this->getDataOrTerminateWithError($result)); $res = current($this->getDataOrTerminateWithError($result));
$studiengang_kz = $res->studiengang_kz; $studiengang_kz = $res->studiengang_kz;
@@ -332,6 +334,7 @@ class Documents extends FHCAPI_Controller
if ($prestudent_id) { if ($prestudent_id) {
$this->load->model('crm/prestudent_model', 'PrestudentModel'); $this->load->model('crm/prestudent_model', 'PrestudentModel');
$this->PrestudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz', 'LEFT'); $this->PrestudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz', 'LEFT');
$this->PrestudentModel->addSelect('tbl_prestudent.*, UPPER(typ || kurzbz) AS kuerzel');
$result = $this->PrestudentModel->load($prestudent_id); $result = $this->PrestudentModel->load($prestudent_id);
$prestudent = current($this->getDataOrTerminateWithError($result)); $prestudent = current($this->getDataOrTerminateWithError($result));
@@ -0,0 +1,121 @@
<?php
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
* This controller operates between (interface) the JS (GUI) and the back-end
* Provides data to the ajax get calls about addresses
* This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
*/
class Board extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'list' => 'dashboard/admin:r',
'create' => 'dashboard/admin:rw',
'update' => 'dashboard/admin:rw',
'delete' => 'dashboard/admin:rw'
]);
// Models
$this->load->model('dashboard/Dashboard_model', 'DashboardModel');
}
public function list()
{
$result = $this->DashboardModel->load();
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result);
}
public function create()
{
$dashboard_kurzbz = $this->input->post('dashboard_kurzbz');
$result = $this->DashboardModel->insert([
'dashboard_kurzbz' => $dashboard_kurzbz
]);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
public function update()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('dashboard_id', 'Dashboard ID', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$dashboard_id = $this->input->post('dashboard_id');
$dashboard_kurzbz = $this->input->post('dashboard_kurzbz');
$beschreibung = $this->input->post('beschreibung');
$result = $this->DashboardModel->update([
'dashboard_id' => $dashboard_id
], [
'dashboard_kurzbz' => $dashboard_kurzbz,
'beschreibung' => $beschreibung
]);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result);
}
public function delete()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('dashboard_id', 'Dashboard ID', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$dashboard_id = $this->input->post('dashboard_id');
//delete all presets
$this->load->model('dashboard/Dashboard_Preset_model', 'DashboardPresetModel');
$result = $this->DashboardPresetModel->delete([
'dashboard_id' => $dashboard_id
]);
$this->getDataOrTerminateWithError($result);
//delete all widgets
$this->load->model('dashboard/Dashboard_Widget_model', 'DashboardWidgetModel');
$result = $this->DashboardWidgetModel->delete([
'dashboard_id' => $dashboard_id
]);
$this->getDataOrTerminateWithError($result);
$result = $this->DashboardModel->delete($dashboard_id);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result);
}
}
@@ -0,0 +1,200 @@
<?php
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
* This controller operates between (interface) the JS (GUI) and the back-end
* Provides data to the ajax get calls about addresses
* This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
*/
class Preset extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'list' => 'dashboard/admin:r',
'getBatch' => 'dashboard/admin:r',
'addWidget' => 'dashboard/admin:rw',
'removeWidget' => 'dashboard/admin:rw'
]);
// Load language phrases
$this->loadPhrases([
'ui'
]);
// Libraries
$this->load->library('dashboard/DashboardLib');
// Models
$this->load->model('ressource/Funktion_model', 'FunktionModel');
}
public function list($dashboard_kurzbz)
{
$sql = "
WITH
dashboard_presets AS (
SELECT
*
FROM
dashboard.tbl_dashboard_preset dp
JOIN
dashboard.tbl_dashboard d ON d.dashboard_id = dp.dashboard_id
WHERE
d.dashboard_kurzbz = {$this->db->escape($dashboard_kurzbz)}
),
general AS (
SELECT
'general' AS funktion_kurzbz,
'Allgemein' AS beschreibung
)
(
SELECT
f.funktion_kurzbz,
f.beschreibung,
COUNT(p.preset_id) AS has_preset
FROM
general f
LEFT JOIN
dashboard_presets p ON p.funktion_kurzbz IS NULL
GROUP BY
f.funktion_kurzbz, f.beschreibung
)
UNION ALL
(
SELECT
f.funktion_kurzbz,
f.beschreibung,
COUNT(p.preset_id) AS has_preset
FROM
public.tbl_funktion f
LEFT JOIN
dashboard_presets p ON p.funktion_kurzbz = f.funktion_kurzbz
GROUP BY
f.funktion_kurzbz, f.beschreibung
ORDER BY
f.beschreibung ASC
)
";
$result = $this->FunktionModel->execReadOnlyQuery($sql);
$funktionen = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($funktionen);
}
public function getBatch()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('db', 'Dashboard', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$db = $this->input->post('db');
$funktionen = $this->input->post('funktionen') ?: [];
$result = [];
foreach ($funktionen as $funktion) {
$conf = $this->dashboardlib->getPreset($db, $funktion);
if ($conf) {
$preset = json_decode($conf->preset, true);
if (!isset($preset[$funktion]) || !isset($preset[$funktion]['widgets']))
$result[$funktion] = [];
else
$result[$funktion] = $preset[$funktion]['widgets'];
} else {
$result[$funktion] = [];
}
}
return $this->terminateWithSuccess($result);
}
public function addWidget()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('dashboard', 'Dashboard', 'required');
$this->form_validation->set_rules('funktion_kurzbz', 'Funktion', 'required');
$this->form_validation->set_rules('widget[widget]', 'Widget', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$dashboard_kurzbz = $this->input->post('dashboard');
$funktion_kurzbz = $this->input->post('funktion_kurzbz');
$widget = $this->input->post('widget');
if (!isset($widget['widgetid']))
$widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz);
$preset = $this->dashboardlib->getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz);
$preset_decoded = json_decode($preset->preset, true);
$this->dashboardlib->addWidgetsToWidgets($preset_decoded, $dashboard_kurzbz, $funktion_kurzbz, [$widget]);
$preset->preset = json_encode($preset_decoded);
$result = $this->dashboardlib->insertOrUpdatePreset($preset);
$this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($widget['widgetid']);
}
public function removeWidget()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('db', 'Dashboard', 'required');
$this->form_validation->set_rules('funktion_kurzbz', 'Funktion', 'required');
$this->form_validation->set_rules('widgetid', 'Widget', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$dashboard_kurzbz = $this->input->post('db');
$funktion_kurzbz = $this->input->post('funktion_kurzbz');
$widgetid = $this->input->post('widgetid');
$preset = $this->dashboardlib->getPreset($dashboard_kurzbz, $funktion_kurzbz);
if (!$preset)
show_404();
$preset_decoded = json_decode($preset->preset, true);
if (!$this->dashboardlib->removeWidgetFromWidgets($preset_decoded, $funktion_kurzbz, $widgetid))
show_404();
$preset->preset = json_encode($preset_decoded);
$result = $this->dashboardlib->insertOrUpdatePreset($preset);
$this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess(array('msg' => $this->p->t('dashboard', 'success_savePreset')));
}
}
@@ -0,0 +1,159 @@
<?php
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
* This controller operates between (interface) the JS (GUI) and the back-end
* Provides data to the ajax get calls about the users dashboard
* This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
*/
class User extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'get' => 'dashboard/benutzer:r',
'addWidget' => 'dashboard/benutzer:rw',
'removeWidget' => 'dashboard/benutzer:rw'
]);
// Libraries
$this->load->library('dashboard/DashboardLib');
// Models
$this->load->model('ressource/Funktion_model', 'FunktionModel');
}
public function get($dashboard_kurzbz)
{
$dashboard = $this->dashboardlib->getDashboardByKurzbz($dashboard_kurzbz);
if (!$dashboard)
show_404();
$uid = $this->authlib->getAuthObj()->username;
/*$mergedconfig = $this->dashboardlib->getMergedConfig($dashboard->dashboard_id, $uid);
$this->terminateWithSuccess([
'general' => call_user_func_array(
'array_merge_recursive',
$mergedconfig
)
]);*/
$defaultconfig = $this->dashboardlib->getDefaultConfig($dashboard->dashboard_id);
$userconfig = $this->dashboardlib->getUserConfig($dashboard->dashboard_id, $uid);
$defaultconfig_squashed = $defaultconfig ? call_user_func_array('array_replace_recursive', $defaultconfig) : [];
$userconfig_squashed = $userconfig ? call_user_func_array('array_replace_recursive', $userconfig) : [];
$mergedconfig = array_replace_recursive($defaultconfig_squashed, $userconfig_squashed);
$this->terminateWithSuccess([
DashboardLib::SECTION_IF_FUNKTION_KURZBZ_IS_NULL => $mergedconfig
]);
}
public function addWidget()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('dashboard', 'Dashboard', 'required');
$this->form_validation->set_rules('widget[widget]', 'Widget', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$widget = $this->input->post('widget');
$dashboard_kurzbz = $this->input->post('dashboard');
$uid = $this->authlib->getAuthObj()->username;
if (!isset($widget['widgetid']))
$widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz);
$override = $this->dashboardlib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
$override_decoded = json_decode($override->override, true);
if (!isset($override_decoded['general']) || !is_array($override_decoded['general']))
$override_decoded['general'] = [];
if (!isset($override_decoded['general']['widgets']))
$override_decoded['general']['widgets'] = [];
$override_decoded['general']['widgets'][$widget['widgetid']] = $widget;
// NOTE(chris): remove doubles in other funktionen
foreach ($override_decoded as $funktion => $array) {
if ($funktion == 'general')
continue;
if (isset($array['widgets']) && isset($array['widgets'][$widget['widgetid']]))
unset($override_decoded[$funktion]['widgets'][$widget['widgetid']]);
}
$override->override = json_encode($override_decoded);
$result = $this->dashboardlib->insertOrUpdateOverride($override);
$this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($widget['widgetid']);
}
public function removeWidget()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('dashboard', 'Dashboard', 'required');
$this->form_validation->set_rules('widget', 'Widget', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$widget_id = $this->input->post('widget');
$dashboard_kurzbz = $this->input->post('dashboard');
$uid = $this->authlib->getAuthObj()->username;
$override = $this->dashboardlib->getOverride($dashboard_kurzbz, $uid);
if (!$override)
show_404();
$override_decoded = json_decode($override->override, true);
foreach (array_keys($override_decoded) as $k) {
if (!isset($override_decoded[$k]["widgets"])) {
unset($override_decoded[$k]);
continue;
}
if (isset($override_decoded[$k]["widgets"][$widget_id])) {
unset($override_decoded[$k]["widgets"][$widget_id]);
}
if (!$override_decoded[$k]["widgets"]) {
unset($override_decoded[$k]);
}
}
$override->override = json_encode($override_decoded);
$result = $this->dashboardlib->insertOrUpdateOverride($override);
$this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess();
}
}
@@ -0,0 +1,137 @@
<?php
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
* This controller operates between (interface) the JS (GUI) and the back-end
* Provides data to the ajax get calls about the users dashboard
* This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
*/
class Widget extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'get' => ['dashboard/benutzer:r', 'dashboard/admin:r'],
'list' => 'dashboard/admin:r',
'listAllowed' => ['dashboard/benutzer:rw', 'dashboard/admin:r'],
'setAllowed' => 'dashboard/admin:rw'
]);
// Libraries
$this->load->library('dashboard/DashboardLib');
// Models
$this->load->model('dashboard/Widget_model', 'WidgetModel');
}
public function get($id)
{
$result = $this->WidgetModel->load($id);
$widget = $this->getDataOrTerminateWithError($result);
if (!$widget)
return $this->terminateWithSuccess([
"widget_id" => 0,
"widget_kurzbz" => "notfound",
"arguments" => [
"className" => 'alert-danger',
"title" => 'Widget Not Found',
"msg" => 'The widget with the id ' . $id . ' could not be found'
],
"setup" => [
"name" => 'Widget Not Found',
"file" => absoluteJsImportUrl('public/js/components/DashboardWidget/Default.js'),
"width" => 1,
"height" => 1
]
]);
$widget = current($widget);
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
$this->terminateWithSuccess($widget);
}
public function list($dashboard)
{
$result = $this->WidgetModel->getWithAllowedForDashboard($dashboard);
$widgets = $this->getDataOrTerminateWithError($result);
$widgets = array_map(function ($widget) {
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $widget;
}, $widgets);
$this->terminateWithSuccess($widgets);
}
public function listAllowed($dashboard)
{
$result = $this->WidgetModel->getForDashboard($dashboard);
$widgets = $this->getDataOrTerminateWithError($result);
$widgets = array_map(function ($widget) {
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $widget;
}, $widgets);
$this->terminateWithSuccess($widgets);
}
public function setAllowed()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('dashboard_id', 'Dashboard', 'required');
$this->form_validation->set_rules('widget_id', 'Widget', 'required');
$this->form_validation->set_rules('allowed', 'Allowed', 'is_bool');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$data = [
'dashboard_id' => $this->input->post('dashboard_id'),
'widget_id' => $this->input->post('widget_id')
];
$this->load->model('dashboard/Dashboard_Widget_model', 'DashboardWidgetModel');
if ($this->input->post('allowed'))
$result = $this->DashboardWidgetModel->insert($data);
else
$result = $this->DashboardWidgetModel->delete($data);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
}
@@ -24,6 +24,7 @@ class NotizPerson extends Notiz_Controller
//Load Models //Load Models
$this->load->model('person/Benutzer_model', 'BenutzerModel'); $this->load->model('person/Benutzer_model', 'BenutzerModel');
$this->load->model('crm/Student_model', 'StudentModel'); $this->load->model('crm/Student_model', 'StudentModel');
$this->load->model('crm/Prestudent_model', 'PrestudentModel');
//Permission checks for allowed Oes //Permission checks for allowed Oes
if ($this->router->method == 'addNewNotiz') if ($this->router->method == 'addNewNotiz')
@@ -38,7 +39,7 @@ class NotizPerson extends Notiz_Controller
{ {
return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Person ID']), self::ERROR_TYPE_GENERAL); return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Person ID']), self::ERROR_TYPE_GENERAL);
} }
$this->_checkIfBerechtigungForOneUidExists($person_id, $allowedStgs); $this->_checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs);
} }
if ( $this->router->method == 'updateNotiz') if ( $this->router->method == 'updateNotiz')
@@ -59,7 +60,7 @@ class NotizPerson extends Notiz_Controller
$person_id = current($data)->person_id; $person_id = current($data)->person_id;
$allowedStgs = $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: []; $allowedStgs = $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: [];
$this->_checkIfBerechtigungForOneUidExists($person_id, $allowedStgs); $this->_checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs);
} }
if ($this->router->method == 'deleteNotiz' ) if ($this->router->method == 'deleteNotiz' )
@@ -78,7 +79,7 @@ class NotizPerson extends Notiz_Controller
} }
$allowedStgs = $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: []; $allowedStgs = $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: [];
$this->_checkIfBerechtigungForOneUidExists($person_id, $allowedStgs); $this->_checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs);
} }
} }
@@ -99,44 +100,20 @@ class NotizPerson extends Notiz_Controller
} }
//stv: if person has permission of one studiengang of person -> permission to add/update/delete Note //stv: if person has permission of one studiengang of person -> permission to add/update/delete Note
private function _checkIfBerechtigungForOneUidExists($person_id, $allowedStgs) private function _checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs)
{ {
//get all studentUids of person_id $result = $this->PrestudentModel->loadWhere(['person_id' => $person_id]);
$result = $this->BenutzerModel->loadWhere(['person_id' => $person_id]);
$data = $this->getDataOrTerminateWithError($result); $data = $this->getDataOrTerminateWithError($result);
$checkarray = []; $checkarray = [];
foreach ($data as $item) foreach ($data as $item)
{ {
//check if isStudent if(in_array($item->studiengang_kz, $allowedStgs))
$result = $this->StudentModel->isStudent($item->uid);
$isStudent = $this->getDataOrTerminateWithError($result);
if($isStudent)
{ {
$checkarray[] = $this->_checkAllowedStgsFromUid($item->uid, $allowedStgs); return true;
} }
} }
if (!in_array(1, $checkarray))
return $this->terminateWithError($this->p->t('ui', 'error_keineBerechtigungStg'), self::ERROR_TYPE_GENERAL);
}
private function _checkAllowedStgsFromUid($student_uid, $allowedStgs) $this->terminateWithError($this->p->t('ui', 'error_keineBerechtigungStg'), self::ERROR_TYPE_GENERAL);
{
$this->load->model('crm/Student_model', 'StudentModel');
$result = $this->StudentModel->loadWhere(['student_uid' => $student_uid]);
$data = $this->getDataOrTerminateWithError($result);
$studiengang_kz = current($data)->studiengang_kz;
if (!in_array($studiengang_kz, $allowedStgs))
{
return 0;
}
else
{
return 1;
}
} }
} }
@@ -0,0 +1,52 @@
<?php
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*/
class Admin extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
// Set required permissions
parent::__construct(
array(
'index' => 'dashboard/admin:rw',
'preview' => 'dashboard/admin:r',
)
);
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
public function index()
{
$this->load->view('dashboard/admin.php', []);
}
public function preview($dashboard_kurzbz = 'CIS')
{
$this->load->view('dashboard/preview.php', [
'dashboard_kurzbz' => $dashboard_kurzbz
]);
}
}
-76
View File
@@ -1,76 +0,0 @@
<?php
defined('BASEPATH') || exit('No direct script access allowed');
class Api extends Auth_Controller
{
public function __construct()
{
parent::__construct(
array(
'index' => 'dashboard/admin:rw',
'getNews' => 'dashboard/benutzer:r',
'getAmpeln' => 'dashboard/benutzer:r',
)
);
$this->load->library('AuthLib', null, 'AuthLib');
$this->_setAuthUID();
}
public function index()
{
echo 'Dashboard API Controller';
}
/**
* Get News.
*/
public function getNews()
{
$limit = $this->input->get('limit');
$this->load->model('content/News_model', 'NewsModel');
$result = $this->NewsModel->getAll($limit);
if (hasData($result))
{
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
}
else
{
$this->terminateWithJsonError('fehler entdeckt');
}
}
/**
* Get Ampeln.
*/
public function getAmpeln()
{
$this->load->model('content/Ampel_model', 'AmpelModel');
$result = $this->AmpelModel->getByUser($this->_uid);
if (hasData($result))
{
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
}
else
{
$this->terminateWithJsonError('fehler entdeckt');
}
}
/**
* Retrieve the UID of the logged user and checks if it is valid
*/
private function _setAuthUID()
{
$this->_uid = getAuthUID();
if (!$this->_uid) show_error('User authentification failed');
}
}
@@ -1,216 +0,0 @@
<?php
defined('BASEPATH') || exit('No direct script access allowed');
/**
* Description of Config
*
* @author bambi
*/
class Config extends Auth_Controller
{
public function __construct()
{
parent::__construct(
array(
'index' => 'dashboard/benutzer:r',
'dummy' => 'dashboard/benutzer:r',
'genWidgetId' => 'dashboard/benutzer:rw',
'addWidgetsToPreset' => 'dashboard/admin:rw',
'removeWidgetFromPreset' => 'dashboard/admin:rw',
'addWidgetsToUserOverride' => 'dashboard/benutzer:rw',
'removeWidgetFromUserOverride' => 'dashboard/benutzer:rw',
'funktionen' => 'dashboard/admin:r',
'preset' => 'dashboard/admin:r',
'presetBatch' => 'dashboard/admin:r'
)
);
$this->load->library('dashboard/DashboardLib', null, 'DashboardLib');
$this->load->library('AuthLib', null, 'AuthLib');
$this->load->model('ressource/Funktion_model', 'FunktionModel');
}
public function index()
{
$dashboard_kurzbz = $this->input->get('db');
$uid = $this->AuthLib->getAuthObj()->username;
$dashboard = $this->DashboardLib->getDashboardByKurzbz($dashboard_kurzbz);
if(!$dashboard) {
http_response_code(404);
$this->terminateWithJsonError(array(
'error' => 'Dashboard ' . $dashboard_kurzbz . ' not found.'
));
}
$mergedconfig = $this->DashboardLib->getMergedConfig($dashboard->dashboard_id, $uid);
$this->outputJsonSuccess($mergedconfig);
}
public function genWidgetId()
{
$dashboard_kurzbz = $this->input->get('db');
$widgetid = $this->DashboardLib->generateWidgetId($dashboard_kurzbz);
$this->outputJsonSuccess(array(
'widgetid' => $widgetid
));
}
public function addWidgetsToPreset()
{
$input = json_decode($this->input->raw_input_stream);
$dashboard_kurzbz = $input->db;
$funktion_kurzbz = $input->funktion_kurzbz;
$preset = $this->DashboardLib->getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz);
$preset_decoded = json_decode($preset->preset, true);
$this->DashboardLib->addWidgetsToWidgets($preset_decoded, $dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
$preset->preset = json_encode($preset_decoded);
$result = $this->DashboardLib->insertOrUpdatePreset($preset);
if (isError($result)) {
http_response_code(500);
$this->terminateWithJsonError('preset could not be saved');
}
$this->outputJsonSuccess(array('msg' => 'preset successfully stored.', 'data' => $preset_decoded));
}
public function removeWidgetFromPreset()
{
$input = json_decode($this->input->raw_input_stream);
$dashboard_kurzbz = $input->db;
$funktion_kurzbz = $input->funktion_kurzbz;
$widgetid = $input->widgetid;
$preset = $this->DashboardLib->getPreset($dashboard_kurzbz, $funktion_kurzbz);
if ($preset === null) {
http_response_code(404);
$this->terminateWithJsonError('preset for dashboard ' . $dashboard_kurzbz . ' and funktion ' . $funktion_kurzbz . ' not found.');
}
$preset_decoded = json_decode($preset->preset, true);
if (!$this->DashboardLib->removeWidgetFromWidgets($preset_decoded, $funktion_kurzbz, $widgetid))
{
http_response_code(404);
$this->terminateWithJsonError('widgetid ' . $widgetid . ' not found');
}
$preset->preset = json_encode($preset_decoded);
$result = $this->DashboardLib->insertOrUpdatePreset($preset);
if (isError($result))
{
http_response_code(500);
$this->terminateWithJsonError('failed to remove widget');
}
$this->outputJsonSuccess(array('msg' => 'preset successfully updated.'));
}
public function addWidgetsToUserOverride()
{
$input = json_decode($this->input->raw_input_stream);
$dashboard_kurzbz = $input->db;
$funktion_kurzbz = $input->funktion_kurzbz;
$uid = $this->AuthLib->getAuthObj()->username;
$override = $this->DashboardLib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
$override_decoded = json_decode($override->override, true);
$this->DashboardLib->addWidgetsToWidgets($override_decoded, $dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
$override->override = json_encode($override_decoded);
$result = $this->DashboardLib->insertOrUpdateOverride($override);
if (isError($result)) {
http_response_code(500);
$this->terminateWithJsonError('override could not be saved');
}
$this->outputJsonSuccess(array('msg' => 'override successfully stored.', 'data' => $override_decoded));
}
public function removeWidgetFromUserOverride()
{
$input = json_decode($this->input->raw_input_stream);
$dashboard_kurzbz = $input->db;
$funktion_kurzbz = $input->funktion_kurzbz;
$uid = $this->AuthLib->getAuthObj()->username;
$widgetid = $input->widgetid;
$override = $this->DashboardLib->getOverride($dashboard_kurzbz, $uid);
if (empty($override)) {
http_response_code(404);
$this->terminateWithJsonError('userconfig for dashboard ' . $dashboard_kurzbz . ' not found.');
}
$override_decoded = json_decode($override->override, true);
if (!$this->DashboardLib->removeWidgetFromWidgets($override_decoded, $funktion_kurzbz, $widgetid))
{
http_response_code(404);
$this->terminateWithJsonError('widgetid ' . $widgetid . ' not found');
}
$override->override = json_encode($override_decoded);
$result = $this->DashboardLib->insertOrUpdateOverride($override, $uid);
if (isError($result))
{
http_response_code(500);
$this->terminateWithJsonError('failed to remove widget');
}
$this->outputJsonSuccess(array('msg' => 'override successfully updated.'));
}
public function funktionen()
{
$funktionen = $this->FunktionModel->load();
if (isError($funktionen)) {
http_response_code(404);
$this->terminateWithJsonError([
'error' => getError($funktionen)
]);
}
return $this->outputJsonSuccess(getData($funktionen) ?: []);
}
public function preset()
{
$db = $this->input->get('db');
$funktion = $this->input->get('funktion');
$conf = $this->DashboardLib->getPreset($db, $funktion);
if (!$conf)
return $this->outputJsonSuccess(['widgets' => [$funktion => []]]);
return $this->outputJsonSuccess(json_decode($conf->preset, true));
}
public function presetBatch()
{
$db = $this->input->get('db');
$funktionen = $this->input->get('funktionen');
$result = [];
foreach ($funktionen as $funktion) {
$conf = $this->DashboardLib->getPreset($db, $funktion);
if ($conf)
{
$preset = json_decode($conf->preset, true);
if (!isset($preset[$funktion]) || !isset($preset[$funktion]['widgets']))
$result[$funktion] = [];
else
$result[$funktion] = $preset[$funktion]['widgets'];
}
else
$result[$funktion] = [];
}
return $this->outputJsonSuccess($result);
}
}
@@ -1,86 +0,0 @@
<?php
defined('BASEPATH') || exit('No direct script access allowed');
/**
* Description of Widget
*
* @author chris
*/
class Dashboard extends Auth_Controller
{
public function __construct()
{
parent::__construct(
array(
'index' => 'dashboard/admin:r',
'create' => 'dashboard/admin:rw',
'update' => 'dashboard/admin:rw',
'delete' => 'dashboard/admin:rw'
)
);
$this->load->library('dashboard/DashboardLib', null, 'DashboardLib');
$this->load->model('dashboard/Dashboard_model', 'DashboardModel');
}
public function index()
{
$result = $this->DashboardModel->load();
if (isError($result)) {
http_response_code(404);
$this->terminateWithJsonError([
'error' => getError($result)
]);
}
return $this->outputJsonSuccess(getData($result) ?: []);
}
public function create()
{
$input = $this->getPostJSON();
$result = $this->DashboardModel->insert($input);
if (isError($result)) {
http_response_code(404);
$this->terminateWithJsonError([
'error' => getError($result)
]);
}
return $this->outputJsonSuccess(getData($result) ?: []);
}
public function update()
{
$input = $this->getPostJSON();
$result = $this->DashboardModel->update($input->dashboard_id, $input);
if (isError($result)) {
http_response_code(404);
$this->terminateWithJsonError([
'error' => getError($result)
]);
}
return $this->outputJsonSuccess(getData($result) ?: []);
}
public function delete()
{
$input = $this->getPostJSON();
$result = $this->DashboardModel->delete($input->dashboard_id);
if (isError($result)) {
http_response_code(404);
$this->terminateWithJsonError([
'error' => getError($result)
]);
}
return $this->outputJsonSuccess(getData($result) ?: []);
}
}
@@ -1,58 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*/
class DashboardDemo extends Auth_Controller
{
private $_uid; // uid of the logged user
/**
* Constructor
*/
public function __construct()
{
// Set required permissions
parent::__construct(
array(
'index' => 'dashboard/benutzer:r',
'admin' => 'dashboard/admin:rw'
)
);
$this->load->library('AuthLib');
$this->load->library('WidgetLib');
$this->_setAuthUID(); // sets property uid
$this->setControllerId(); // sets the controller id
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
public function index()
{
$this->load->view('dashboard/dashboard_demo.php', []);
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
public function admin()
{
$this->load->view('dashboard/dashboard_demo_admin.php', []);
}
// -----------------------------------------------------------------------------------------------------------------
// Private methods
/**
* Retrieve the UID of the logged user and checks if it is valid
*/
private function _setAuthUID()
{
$this->_uid = getAuthUID();
if (!$this->_uid) show_error('User authentification failed');
}
}
@@ -1,134 +0,0 @@
<?php
defined('BASEPATH') || exit('No direct script access allowed');
/**
* Description of Widget
*
* @author chris
*/
class Widget extends Auth_Controller
{
public function __construct()
{
parent::__construct(
array(
'index' => ['dashboard/benutzer:r', 'dashboard/admin:r'],
'getAll' => 'dashboard/admin:r',
'getWidgetsForDashboard' => ['dashboard/benutzer:rw', 'dashboard/admin:r'],
'setAllowed' => 'dashboard/admin:rw'
)
);
$this->load->library('dashboard/DashboardLib', null, 'DashboardLib');
$this->load->model('dashboard/Widget_model', 'WidgetModel');
$this->load->model('dashboard/Dashboard_Widget_model', 'DashboardWidgetModel');
}
public function index()
{
$widget_id = $this->input->get('id');
$widget = $this->WidgetModel->load($widget_id);
if (isError($widget) || !getData($widget))
return $this->outputJsonSuccess([
"widget_id" => 0,
"widget_kurzbz" => "notfound",
"arguments" => [
"className" => 'alert-danger',
"title" => 'Widget Not Found',
"msg" => 'The widget with the id ' . $widget_id . ' could not be found'
],
"setup" => [
"name" => 'Widget Not Found',
"file" => absoluteJsImportUrl('public/js/components/DashboardWidget/Default.js'),
"width" => 1,
"height" => 1
]
]);
$widget = current(getData($widget));
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $this->outputJsonSuccess($widget);
}
public function getAll()
{
$dashboard_id = $this->input->get('dashboard_id');
$result = $this->WidgetModel->getWithAllowedForDashboard($dashboard_id);
if (isError($result))
return $this->outputJsonError(getError($result));
$tmpwidgets = getData($result) ?: [];
$widgets = array_map(function($widget) {
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $widget;
}, $tmpwidgets);
$this->outputJsonSuccess($widgets);
}
public function getWidgetsForDashboard()
{
$db = $this->input->get('db');
$result = $this->WidgetModel->getForDashboard($db);
if (isError($result)) {
http_response_code(404);
$this->terminateWithJsonError([
'error' => getError($result)
]);
}
$tmpwidgets = getData($result) ?: [];
$widgets = array_map(function($widget) {
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $widget;
}, $tmpwidgets);
$this->outputJsonSuccess($widgets);
}
public function setAllowed()
{
$input = $this->getPostJSON();
$dashboard_id = $input->dashboard_id;
$widget_id = $input->widget_id;
$action = $input->action;
if ($action == 'add') {
$result = $this->DashboardWidgetModel->insert([
'dashboard_id' => $dashboard_id,
'widget_id' => $widget_id
]);
} elseif ($action == 'delete') {
$result = $this->DashboardWidgetModel->delete([
'dashboard_id' => $dashboard_id,
'widget_id' => $widget_id
]);
} else {
http_response_code(404); // TODO(chris): 400?
$this->terminateWithJsonError([
'error' => 'action value invalid'
]);
}
if (isError($result)) {
http_response_code(404);
$this->terminateWithJsonError([
'error' => getError($result)
]);
}
return $this->outputJsonSuccess(getData($result));
}
}
@@ -495,6 +495,10 @@ class AbgabetoolJob extends JOB_Controller
// get all new or changed termine in interval // get all new or changed termine in interval
$result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSince($interval, $relevantTypes); $result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSince($interval, $relevantTypes);
$retval = getData($result); $retval = getData($result);
if(!$retval) {
$this->_ci->logInfo("Keine Emails an Betreuer über neue oder veränderte Termine versandt");
return;
}
// group changed/new abgaben for projektarbeiten // group changed/new abgaben for projektarbeiten
$projektarbeiten = []; $projektarbeiten = [];
@@ -557,6 +561,8 @@ class AbgabetoolJob extends JOB_Controller
$anredeFillString = $data->anrede == "Herr" ? "r" : ""; $anredeFillString = $data->anrede == "Herr" ? "r" : "";
$fullFormattedNameString = $data->first; $fullFormattedNameString = $data->first;
$relevantCounter = 0; // workaround to check if a betreuer needs to have any notification about relevant
// abgaben at all to avoid sending empty emails since we filter on certain conditions
forEach($tupelArr as $tupel) { forEach($tupelArr as $tupel) {
$projektarbeit_id = $tupel[0]; $projektarbeit_id = $tupel[0];
$betreuerRow = $tupel[1]; $betreuerRow = $tupel[1];
@@ -575,6 +581,8 @@ class AbgabetoolJob extends JOB_Controller
continue; continue;
} }
$relevantCounter++;
// format the Student Name // format the Student Name
$s = $relevantAbgaben[0]; $s = $relevantAbgaben[0];
$nameParts = []; $nameParts = [];
@@ -633,6 +641,11 @@ class AbgabetoolJob extends JOB_Controller
// done with building the change list, now send it // done with building the change list, now send it
$betreuerRow = $tupelArr[0][1]; $betreuerRow = $tupelArr[0][1];
if($relevantCounter == 0) {
$this->_ci->logInfo('No Relevant Abgaben to notify Betreuer PersonID: "'.$betreuerRow->person_id.'".');
continue;
}
$path = $this->_ci->config->item('URL_MITARBEITER'); $path = $this->_ci->config->item('URL_MITARBEITER');
$url = CIS_ROOT.$path; $url = CIS_ROOT.$path;
+16 -1
View File
@@ -50,6 +50,7 @@ class PermissionLib
const LOGINAS_PERSONIDS_BLACKLIST = 'permission_loginas_personids_blacklist'; const LOGINAS_PERSONIDS_BLACKLIST = 'permission_loginas_personids_blacklist';
private $_ci; // CI instance private $_ci; // CI instance
private $access_rights; // current users access rights
private static $bb; // benutzerberechtigung private static $bb; // benutzerberechtigung
/** /**
@@ -61,6 +62,8 @@ class PermissionLib
// Loads CI instance // Loads CI instance
$this->_ci =& get_instance(); $this->_ci =& get_instance();
$this->access_rights = null;
$this->_ci->config->load('permission'); // Loads permission configuration $this->_ci->config->load('permission'); // Loads permission configuration
// If it's NOT called from command line // If it's NOT called from command line
@@ -69,8 +72,10 @@ class PermissionLib
// API Caller rights initialization // API Caller rights initialization
$authObj = $this->_ci->authlib->getAuthObj(); $authObj = $this->_ci->authlib->getAuthObj();
self::$bb = new benutzerberechtigung(); self::$bb = new benutzerberechtigung();
if ($authObj) if ($authObj) {
self::$bb->getBerechtigungen($authObj->{AuthLib::AO_USERNAME}); self::$bb->getBerechtigungen($authObj->{AuthLib::AO_USERNAME});
$this->access_rights = self::$bb->berechtigungen;
}
} }
} }
@@ -340,6 +345,16 @@ class PermissionLib
} }
} }
/**
* Returns the access rights for the current user
*
* @return array|null
*/
public function getAccessRights()
{
return $this->access_rights;
}
//------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------
// Private methods // Private methods
@@ -49,7 +49,7 @@ class DashboardLib
public function getMergedConfig($dashboard_id, $uid) public function getMergedConfig($dashboard_id, $uid)
{ {
$defaultconfig = $this->getDefaultConfig($dashboard_id, $uid); $defaultconfig = $this->getDefaultConfig($dashboard_id);
$userconfig = $this->getUserConfig($dashboard_id, $uid); $userconfig = $this->getUserConfig($dashboard_id, $uid);
$mergedconfig = array_replace_recursive($defaultconfig, $userconfig); $mergedconfig = array_replace_recursive($defaultconfig, $userconfig);
@@ -57,14 +57,31 @@ class DashboardLib
return $mergedconfig; return $mergedconfig;
} }
public function getDefaultConfig($dashboard_id, $uid) public function getDefaultConfig($dashboard_id)
{ {
$res_presets = $this->_ci->DashboardPresetModel->getPresets($dashboard_id, $uid); $funktion_kurzbzs = [];
$rights = $this->_ci->permissionlib->getAccessRights();
if ($rights)
$funktion_kurzbzs = array_unique(array_map(function ($right) {
return $right->funktion_kurzbz;
}, $rights));
$this->_ci->DashboardPresetModel->db
->group_start()
->where_in('funktion_kurzbz', $funktion_kurzbzs)
->or_where('funktion_kurzbz IS NULL')
->group_end();
$this->_ci->DashboardPresetModel->addOrder('funktion_kurzbz', 'DESC');
$result = $this->_ci->DashboardPresetModel->loadWhere([
'dashboard_id' => $dashboard_id
]);
$defaultconfig = array(); $defaultconfig = array();
if (hasData($res_presets)) if (hasData($result))
{ {
$presets = getData($res_presets); $presets = getData($result);
foreach ($presets as $presetobj) foreach ($presets as $presetobj)
{ {
$preset = json_decode($presetobj->preset, true); $preset = json_decode($presetobj->preset, true);
@@ -137,8 +154,10 @@ class DashboardLib
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz); $dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
$funktion_kurzbz = ($section === self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL) ? null : $section; $funktion_kurzbz = ($section === self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL) ? null : $section;
$result = $this->_ci->DashboardPresetModel $result = $this->_ci->DashboardPresetModel->loadWhere([
->getPresetByDashboardAndFunktion($dashboard->dashboard_id, $funktion_kurzbz); 'dashboard_id' => $dashboard->dashboard_id,
'funktion_kurzbz' => $funktion_kurzbz
]);
if (hasData($result)) if (hasData($result))
{ {
@@ -195,11 +214,11 @@ class DashboardLib
{ {
foreach ($addwigets as $widget) foreach ($addwigets as $widget)
{ {
if(!isset($widget->widgetid)) if(!isset($widget['widgetid']))
{ {
$widget->widgetid = $this->generateWidgetId($dashboard_kurzbz); $widget['widgetid'] = $this->generateWidgetId($dashboard_kurzbz);
} }
$this->addWidgetToWidgets($widgets, $section, $widget, $widget->widgetid); $this->addWidgetToWidgets($widgets, $section, $widget, $widget['widgetid']);
} }
} }
@@ -11,57 +11,4 @@ class Dashboard_Preset_model extends DB_Model
$this->dbTable = 'dashboard.tbl_dashboard_preset'; $this->dbTable = 'dashboard.tbl_dashboard_preset';
$this->pk = 'preset_id'; $this->pk = 'preset_id';
} }
/**
* Get Presets of given uid.
* @param integer dashboard_id
* @param string $uid
* @return array
*/
public function getPresets($dashboard_id, $uid)
{
// TODO: get Funktionen for uid and load all preset for all funktionen for uid
//return $this->loadWhere(array('dashboard_id' => $dashboard_id, 'funktion_kurzbz'=> null));
$sql = <<<EOSQL
SELECT
*
FROM
dashboard.tbl_dashboard_preset
WHERE
dashboard_id = ?
AND (
funktion_kurzbz IN (
SELECT
DISTINCT funktion_kurzbz
FROM
public.tbl_benutzerfunktion
WHERE
uid = ?
AND
NOW()::date
BETWEEN
COALESCE(datum_von, '1970-01-01')
AND
COALESCE(datum_bis, '2170-12-31')
)
OR
funktion_kurzbz IS NULL
)
ORDER BY
funktion_kurzbz DESC
EOSQL;
return $this->execQuery($sql, array($dashboard_id, $uid));
}
/**
* Get Preset by Dashboard and Funktion
* @param integer dashboard_id
* @param string funktion_kurzbz
* @return array
*/
public function getPresetByDashboardAndFunktion($dashboard_id, $funktion_kurzbz)
{
return $this->loadWhere(array('dashboard_id' => $dashboard_id, 'funktion_kurzbz' => $funktion_kurzbz));
}
} }
@@ -8,9 +8,15 @@ $this->load->view(
'axios027' => true, 'axios027' => true,
'restclient' => true, 'restclient' => true,
'vue3' => true, 'vue3' => true,
'customJSModules' => ['public/js/apps/Dashboard.js'], 'primevue3' => true,
'vuedatepicker11' => true,
'customJSs' => [
'vendor/moment/luxonjs/luxon.min.js'
],
'customJSModules' => ['public/js/apps/Dashboard/Admin.js'],
'customCSSs' => [ 'customCSSs' => [
'public/css/components/dashboard.css' 'public/css/components/dashboard.css',
'public/css/components/primevue.css',
], ],
'navigationcomponent' => true 'navigationcomponent' => true
) )
@@ -25,7 +31,7 @@ $this->load->view(
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1> <h1 class="h2">Dashboard</h1>
</div> </div>
<core-dashboard dashboard="CIS" apiurl="<?= site_url('dashboard'); ?>"></core-dashboard> <dashboard-admin></dashboard-admin>
</div> </div>
</div> </div>
@@ -8,7 +8,12 @@ $this->load->view(
'axios027' => true, 'axios027' => true,
'restclient' => true, 'restclient' => true,
'vue3' => true, 'vue3' => true,
'customJSModules' => ['public/js/apps/DashboardAdmin.js'], 'vuedatepicker11' => true,
'primevue3' => true,
'customJSs' => [
'vendor/moment/luxonjs/luxon.min.js'
],
'customJSModules' => ['public/js/apps/Dashboard/Preview.js'],
'customCSSs' => [ 'customCSSs' => [
'public/css/components/dashboard.css' 'public/css/components/dashboard.css'
], ],
@@ -23,9 +28,9 @@ $this->load->view(
<div id="content"> <div id="content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1> <h1 class="h2">Dashboard <?= $dashboard_kurzbz ?></h1>
</div> </div>
<dashboard-admin dashboard="CIS" apiurl="<?= site_url('dashboard'); ?>"></dashboard-admin> <core-dashboard dashboard="<?= $dashboard_kurzbz ?>"></core-dashboard>
</div> </div>
</div> </div>
+2
View File
@@ -3550,9 +3550,11 @@ function StudentZeugnisDokumentArchivieren()
case 'microcredentialzertifikat_1': case 'microcredentialzertifikat_1':
case 'microcredentialzertifikat_2': case 'microcredentialzertifikat_2':
case 'microcredentialzertifikat_3': case 'microcredentialzertifikat_3':
case 'microcredentialzertifikat_4':
case 'microcredential_1': case 'microcredential_1':
case 'microcredential_2': case 'microcredential_2':
case 'microcredential_3': case 'microcredential_3':
case 'microcredential_4':
xml = 'microcredential.xml.php'; xml = 'microcredential.xml.php';
break; break;
+162 -7
View File
@@ -552,9 +552,40 @@ class lehreinheitmitarbeiter extends basis_db
$beginn = new DateTime($beginn); $beginn = new DateTime($beginn);
$ende = new DateTime($ende); $ende = new DateTime($ende);
// get relevant Studiensemester
$studiensemester_kurzbz_arr = [];
$qry = '
SELECT
studiensemester_kurzbz
FROM
public.tbl_studiensemester
WHERE
start BETWEEN
'. $this->db_add_param($beginn->format('Y-m-d')). ' AND
'. $this->db_add_param($ende->format('Y-m-d'));
if ($this->db_query($qry))
{
while($row = $this->db_fetch_object())
{
$studiensemester_kurzbz_arr[] = $row->studiensemester_kurzbz;
}
}
else
{
$this->errormsg = 'Fehler bei der Datenbankabfrage';
return false;
}
$lehrgaengeDistr = $this->_getLehrgaengeForDistribution($studiensemester_kurzbz_arr);
if (!is_array($studiensemester_kurzbz_arr) || empty($studiensemester_kurzbz_arr)) return true;
$qry = ' $qry = '
WITH semester_sws_tbl AS ( WITH semester_sws_tbl AS (
SELECT DISTINCT lehreinheit_id, studiensemester_kurzbz, lema.semesterstunden, stg.studiengang_kz SELECT DISTINCT lehreinheit_id, studiensemester_kurzbz, lema.semesterstunden,
stg.studiengang_kz, stg.melde_studiengang_kz, stg.lgartcode
FROM lehre.tbl_lehreinheitmitarbeiter lema FROM lehre.tbl_lehreinheitmitarbeiter lema
JOIN lehre.tbl_lehreinheit USING (lehreinheit_id) JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
JOIN lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id) JOIN lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id)
@@ -564,38 +595,103 @@ class lehreinheitmitarbeiter extends basis_db
JOIN public.tbl_studiengang stg ON stg.studiengang_kz = sto.studiengang_kz JOIN public.tbl_studiengang stg ON stg.studiengang_kz = sto.studiengang_kz
JOIN public.tbl_studiensemester ss USING (studiensemester_kurzbz) JOIN public.tbl_studiensemester ss USING (studiensemester_kurzbz)
WHERE mitarbeiter_uid = '. $this->db_add_param($uid). ' WHERE mitarbeiter_uid = '. $this->db_add_param($uid). '
AND ( AND ss.studiensemester_kurzbz IN ('.$this->implode4SQL($studiensemester_kurzbz_arr).')
ss.start BETWEEN
'. $this->db_add_param($beginn->format('Y-m-d')). ' AND
'. $this->db_add_param($ende->format('Y-m-d')). ')
-- nur lehre, die bisgemeldet wird -- nur lehre, die bisgemeldet wird
AND lema.bismelden AND lema.bismelden
-- keine lehreinheiten ohne semesterstunden -- keine lehreinheiten ohne semesterstunden
AND lema.semesterstunden != 0 AND lema.semesterstunden != 0
) )
SELECT SELECT
studiengang_kz, studiengang_kz,
studiensemester_kurzbz, studiensemester_kurzbz,
melde_studiengang_kz,
lgartcode,
sum(semesterstunden) AS summe, sum(semesterstunden) AS summe,
round(sum(semesterstunden) / 15, 2) AS sws round(sum(semesterstunden) / 15, 2) AS sws
FROM FROM
semester_sws_tbl semester_sws_tbl
GROUP BY GROUP BY
studiengang_kz, studiengang_kz,
studiensemester_kurzbz studiensemester_kurzbz,
melde_studiengang_kz,
lgartcode
ORDER BY ORDER BY
studiengang_kz; studiengang_kz;
'; ';
if ($this->db_query($qry)) if ($this->db_query($qry))
{ {
$additionalLehrgaenge = [];
while($row = $this->db_fetch_object()) while($row = $this->db_fetch_object())
{ {
$obj = new StdClass(); $obj = new StdClass();
$obj->studiengang_kz = $row->studiengang_kz; $obj->studiengang_kz = $row->studiengang_kz;
$obj->studiensemester_kurzbz = $row->studiensemester_kurzbz; $obj->studiensemester_kurzbz = $row->studiensemester_kurzbz;
$obj->melde_studiengang_kz = $row->melde_studiengang_kz;
$obj->lgartcode = $row->lgartcode;
$obj->sws = $row->sws; $obj->sws = $row->sws;
if (isset($lehrgaengeDistr[$uid][$row->studiensemester_kurzbz]))
{
$lehrgaenge = $lehrgaengeDistr[$uid][$row->studiensemester_kurzbz];
foreach ($lehrgaenge as $lehreinheit_id => $lehrgangKzArr)
{
// wenn lehrgang gefunden, zusammenhängende Lehrgaenge holen und sws aufteilen
if (array_key_exists($row->studiengang_kz, $lehrgangKzArr))
{
foreach ($lehrgangKzArr as $studiengang_kz => $lehrgang)
{
// check: nur eine Studiengangsverknüpfung pro Mitarbeiter, Semester, und Referenzstudiengang
if (
$studiengang_kz == $row->studiengang_kz
|| isset(
$additionalLehrgaenge[$uid][$row->studiensemester_kurzbz][$row->studiengang_kz][$studiengang_kz]
)
) continue;
// Lehrgang erstellen
$lg = new StdClass();
$lg->mitarbeiter_uid = $uid;
$lg->melde_studiengang_kz = $lehrgang->melde_studiengang_kz;
$lg->lgartcode = $lehrgang->lgartcode;
$lg->studiengang_kz = $lehrgang->studiengang_kz;
$lg->studiensemester_kurzbz = $lehrgang->studiensemester_kurzbz;
$lg->summe = $row->summe;
$lg->sws = $row->sws;
// Lehrgang, der mit Ursprungsstudiengang aufgrund lehreinheit "verknüpft" ist, hinzufügen
$additionalLehrgaenge[$uid][$row->studiensemester_kurzbz][$row->studiengang_kz][$studiengang_kz] = $lg;
}
}
}
// ignorieren, wenn für den Studiengang keine verknüpften Lehrgaenge hat
if (isset($additionalLehrgaenge[$uid][$row->studiensemester_kurzbz][$row->studiengang_kz]))
{
$addLehrgaenge = $additionalLehrgaenge[$uid][$row->studiensemester_kurzbz][$row->studiengang_kz];
// sws Durchschnitt über alle verknuepften Lehrgaenge berechnet
$summeSws = $row->summe/(count($addLehrgaenge) + 1);
$sws = $row->sws/(count($addLehrgaenge) + 1);
// neue sws zuweisen
$obj->summe = $summeSws;
$obj->sws = $sws;
foreach ($addLehrgaenge as $conn_ws_studiengang_kz => $lehrgang)
{
// sws fuer jeden verknuepften Lehrgang zuweisen
$lehrgang->summe = $summeSws;
$lehrgang->sws = $sws;
// neue lehrgang sws hinzufuegen
$this->result [] = $lehrgang;
}
}
}
$this->result []= $obj; $this->result []= $obj;
} }
return true; return true;
@@ -655,4 +751,63 @@ class lehreinheitmitarbeiter extends basis_db
return false; return false;
} }
/**
* Get "connected" Lehrgaenge for equal sws distribution.
* @param $studiensemester_kurzbz_arr all semester for which Lehrgaenge should be retrieved
* @return object success or error
*/
private function _getLehrgaengeForDistribution($studiensemester_kurzbz_arr)
{
if (!is_array($studiensemester_kurzbz_arr) || empty($studiensemester_kurzbz_arr)) return [];
$qry = "
WITH gruppen AS (
SELECT
mitarbeiter_uid, lehreinheit_id, lehrveranstaltung_id, studiensemester_kurzbz, sem.start, sem.ende,
lehreinheitgruppe_id, studiengang_kz, melde_studiengang_kz, lgartcode
FROM
lehre.tbl_lehreinheitmitarbeiter lema
JOIN lehre.tbl_lehreinheit le USING (lehreinheit_id)
JOIN lehre.tbl_lehreinheitgruppe legr USING (lehreinheit_id)
JOIN public.tbl_studiengang stg USING (studiengang_kz)
JOIN public.tbl_studiensemester sem USING (studiensemester_kurzbz)
WHERE
bismelden
AND stg.melderelevant
AND stg.typ = 'l'
AND le.studiensemester_kurzbz IN (".$this->implode4SQL($studiensemester_kurzbz_arr).")
)
SELECT
DISTINCT mitarbeiter_uid, studiensemester_kurzbz, lehreinheit_id, studiengang_kz, melde_studiengang_kz, lgartcode
FROM
gruppen gr
GROUP BY
mitarbeiter_uid, studiensemester_kurzbz, lehreinheit_id, studiengang_kz, melde_studiengang_kz, lgartcode
ORDER BY
mitarbeiter_uid, studiensemester_kurzbz, lehreinheit_id, studiengang_kz, melde_studiengang_kz, lgartcode";
$lehrgaengeDistributions = [];
if($this->db_query($qry))
{
while($row = $this->db_fetch_object())
{
// group by properties
$lehrgaengeDistributions
[$row->mitarbeiter_uid]
[$row->studiensemester_kurzbz]
[$row->lehreinheit_id]
[$row->studiengang_kz]
= $row;
}
}
else
{
$this->errormsg = 'Fehler bei der Datenbankabfrage';
return false;
}
return $lehrgaengeDistributions;
}
} }
+2
View File
@@ -270,6 +270,8 @@ class LehreListHelper
} else if ($row->bisio_id != '' && $row->status != 'Incoming' && ($row->von > $stsemdatumvon || $row->von == '')) { } else if ($row->bisio_id != '' && $row->status != 'Incoming' && ($row->von > $stsemdatumvon || $row->von == '')) {
// if bis datum is not yet known but von is available already // if bis datum is not yet known but von is available already
$zusatz .= '(o)(ab '.$datum->formatDatum($row->von, 'd.m.Y').')'; $zusatz .= '(o)(ab '.$datum->formatDatum($row->von, 'd.m.Y').')';
} else if ($row->bisio_id != '' && $row->status != 'Incoming' && ($row->von <= $stsemdatumvon || $row->von == '') && ($row->bis == '' || $row->bis > date('Y-m-d'))){
$zusatz .= '(o)(ab '.$datum->formatDatum($row->von, 'd.m.Y').')';
} }
+3
View File
@@ -193,3 +193,6 @@
word-break: break-word; word-break: break-word;
} }
.news-list-item p {
word-break: break-word;
}
+46
View File
@@ -0,0 +1,46 @@
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
list() {
return {
method: 'get',
url: 'api/frontend/v1/dashboard/board/list'
};
},
add(params) {
return {
method: 'post',
url: 'api/frontend/v1/dashboard/board/create',
params
};
},
update(params) {
return {
method: 'post',
url: 'api/frontend/v1/dashboard/board/update',
params
};
},
delete(dashboard_id) {
return {
method: 'post',
url: 'api/frontend/v1/dashboard/board/delete',
params: { dashboard_id }
};
}
}
+47
View File
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
list(dashboard_kurzbz) {
return {
method: 'get',
url: 'api/frontend/v1/dashboard/preset/list/'
+ encodeURIComponent(dashboard_kurzbz)
};
},
getBatch(params) {
return {
method: 'post',
url: 'api/frontend/v1/dashboard/preset/getBatch',
params
};
},
addWidget(params) {
return {
method: 'post',
url: 'api/frontend/v1/dashboard/preset/addWidget',
params
};
},
removeWidget(params) {
return {
method: 'post',
url: 'api/frontend/v1/dashboard/preset/removeWidget',
params
};
}
};
+45
View File
@@ -0,0 +1,45 @@
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
get(dashboard) {
return {
method: 'get',
url: '/api/frontend/v1/dashboard/user/get/' + dashboard
};
},
addWidget(dashboard, widget) {
return {
method: 'post',
url: '/api/frontend/v1/dashboard/user/addWidget',
params: {
dashboard,
widget
}
};
},
removeWidget(dashboard, widget) {
return {
method: 'post',
url: '/api/frontend/v1/dashboard/user/removeWidget',
params: {
dashboard,
widget
}
};
}
};
+46
View File
@@ -0,0 +1,46 @@
/**
* Copyright (C) 2026 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
get(widget) {
return {
method: 'get',
url: '/api/frontend/v1/dashboard/widget/get/' + widget
};
},
list(dashboard) {
return {
method: 'get',
url: '/api/frontend/v1/dashboard/widget/list/' + dashboard
};
},
listAllowed(dashboard) {
return {
method: 'get',
url: '/api/frontend/v1/dashboard/widget/listAllowed/' + dashboard
};
},
setAllowed(dashboard_id, widget_id, allowed) {
return {
method: 'post',
url: '/api/frontend/v1/dashboard/widget/setAllowed',
params: {
dashboard_id, widget_id, allowed
}
};
}
};
+60 -9
View File
@@ -1,16 +1,67 @@
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js'; import { CoreNavigationCmpt } from '../../components/navigation/Navigation.js';
import DashboardAdmin from '../../components/Dashboard/Admin.js'; import DashboardAdmin from '../../components/Dashboard/Admin.js';
import PluginsPhrasen from '../../plugins/Phrasen.js'; import PluginsPhrasen from '../../plugins/Phrasen.js';
import ApiRenderers from '../../api/factory/renderers.js';
const app = Vue.createApp({ const app = Vue.createApp({
name: 'AdminApp', name: 'DashboardAdminApp',
data: () => ({ data: () => ({
appSideMenuEntries: {} appSideMenuEntries: {},
}), renderers: null
components: { }),
CoreNavigationCmpt, components: {
DashboardAdmin CoreNavigationCmpt,
} DashboardAdmin
},
provide() {
return {
// TODO(chris): move those two into the components that need it
renderers: Vue.computed(() => this.renderers),
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone
};
},
created() {
this.$api
.call(ApiRenderers.loadRenderers())
.then(res => {
for (let rendertype of Object.keys(res.data)) {
let modalTitle = null;
let modalContent = null;
let calendarEvent = null;
if (res.data[rendertype].modalTitle)
modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalTitle)));
if (res.data[rendertype].modalContent)
modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalContent)));
if (res.data[rendertype].calendarEvent)
calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].calendarEvent)));
if (res.data[rendertype].calendarEventStyles) {
var head = document.head;
if (!head.querySelector(`link[href="${res.data[rendertype].calendarEventStyles}"]`)) {
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = res.data[rendertype].calendarEventStyles;
head.appendChild(link);
}
}
if (this.renderers === null) {
this.renderers = {};
}
if (!this.renderers[rendertype]) {
this.renderers[rendertype] = {}
}
this.renderers[rendertype].modalTitle = modalTitle;
this.renderers[rendertype].modalContent = modalContent;
this.renderers[rendertype].calendarEvent = calendarEvent;
}
})
.catch(this.$fhcAlert.handleSystemErrors);
}
}); });
app.use(PluginsPhrasen); app.use(PluginsPhrasen);
app.directive('tooltip', primevue.tooltip);
app.mount('#main'); app.mount('#main');
+17
View File
@@ -0,0 +1,17 @@
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import CoreDashboard from '../../components/Dashboard/Dashboard.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
const app = Vue.createApp({
name: 'DashboardPreviewApp',
data: () => ({
appSideMenuEntries: {}
}),
components: {
CoreNavigationCmpt,
CoreDashboard
}
});
app.use(PluginsPhrasen);
app.directive('tooltip', primevue.tooltip);
app.mount('#main');
+1 -1
View File
@@ -17,7 +17,7 @@ export default {
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<button type="button" class="btn btn-primary" @click="result=true;this.hide()">OK</button> <button type="button" class="btn btn-primary" @click="result=true;this.hide()">OK</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{this.$p.t('ui', 'cancel')}}</button>
</template> </template>
</bs-modal>` </bs-modal>`
} }
@@ -180,7 +180,7 @@ export const AbgabetoolAssistenz = {
// frozen: true, // frozen: true,
// width: 40 // width: 40
// }, // },
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', headerFilter: false, headerSort: false, formatter: this.formAction, tooltip:false, minWidth: 150, cssClass: 'sticky-col'}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', headerFilter: false, headerSort: false, formatter: this.formAction, tooltip:false, minWidth: 100, cssClass: 'sticky-col'},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
@@ -226,7 +226,7 @@ export const AbgabetoolAssistenz = {
field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false}, field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
], ],
persistence: false, persistence: false,
persistenceID: "abgabetool_2026_02_26" persistenceID: "abgabetool_2026_03_16"
}, },
abgabeTableEventHandlers: [ abgabeTableEventHandlers: [
{ {
@@ -645,7 +645,7 @@ export const AbgabetoolAssistenz = {
actionButtons.className = "d-flex gap-3"; // you can keep Bootstrap gap if loaded actionButtons.className = "d-flex gap-3"; // you can keep Bootstrap gap if loaded
actionButtons.style.display = "flex"; actionButtons.style.display = "flex";
actionButtons.style.alignItems = "stretch"; // buttons stretch to full height actionButtons.style.alignItems = "stretch"; // buttons stretch to full height
actionButtons.style.justifyContent = "center"; actionButtons.style.justifyContent = "start";
actionButtons.style.height = "100%"; // full grid cell height actionButtons.style.height = "100%"; // full grid cell height
const val = cell.getValue(); const val = cell.getValue();
@@ -675,8 +675,20 @@ export const AbgabetoolAssistenz = {
createButton('fa fa-timeline', 'abgabetool/c4termineTimeLine', () => this.openTimeline(val)) createButton('fa fa-timeline', 'abgabetool/c4termineTimeLine', () => this.openTimeline(val))
); );
if(val.latestTerminWithUpload) {
actionButtons.append(
createButton('fa fa-download', 'abgabetool/c4downloadLatestAbgabe', () => this.downloadAbgabe(val.latestTerminWithUpload.paabgabe_id, val.student_uid, val.projektarbeit_id))
)
}
return actionButtons; return actionButtons;
}, },
downloadAbgabe(paabgabe_id, student_uid, projektarbeit_id) {
const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${paabgabe_id}&student_uid=${student_uid}&projektarbeit_id=${projektarbeit_id}`;
window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url)
// this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
},
undoSelection(cell) { undoSelection(cell) {
// checks if cells row is selected and unselects -> imitates columns which dont trigger row selection // checks if cells row is selected and unselects -> imitates columns which dont trigger row selection
@@ -780,6 +792,8 @@ export const AbgabetoolAssistenz = {
// TODO: mehrsprachig englisch // TODO: mehrsprachig englisch
projekt.note_bez = opt.bezeichnung projekt.note_bez = opt.bezeichnung
} }
const latestTerminWithUpload = this.findLatestTerminWithUpload(projekt)
return { return {
...projekt, ...projekt,
@@ -787,6 +801,7 @@ export const AbgabetoolAssistenz = {
details: { details: {
student_uid: projekt.student_uid, student_uid: projekt.student_uid,
projektarbeit_id: projekt.projektarbeit_id, projektarbeit_id: projekt.projektarbeit_id,
latestTerminWithUpload: latestTerminWithUpload ?? null
}, },
pkz: this.buildPKZ(projekt), pkz: this.buildPKZ(projekt),
beurteilung: projekt.beurteilungLink ?? null, beurteilung: projekt.beurteilungLink ?? null,
@@ -800,6 +815,15 @@ export const AbgabetoolAssistenz = {
} }
}) })
}, },
findLatestTerminWithUpload(projekt) {
const withAbgabedatumSorted = projekt?.abgabetermine?.filter(t => t.abgabedatum != null)?.sort((a,b) => a < b)
if(withAbgabedatumSorted.length) {
return withAbgabedatumSorted[0]
}
return null
},
createInfoString(data) { createInfoString(data) {
let str = ''; let str = '';
@@ -1413,9 +1437,12 @@ export const AbgabetoolAssistenz = {
<div id="abgabetable" style="max-height:40vw;"> <div id="abgabetable" style="max-height:40vw;">
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto me-auto">
<h2 tabindex="1">{{$p.t('abgabetool/abgabetoolTitle')}}</h2> <h2 tabindex="1">{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
</div> </div>
<div class="col-auto">
<label class="col-form-label">{{$capitalize($p.t('lehre/studiengang'))}}:</label>
</div>
<div class="col-3"> <div class="col-3">
<Dropdown <Dropdown
:placeholder="$capitalize($p.t('lehre/studiengang'))" :placeholder="$capitalize($p.t('lehre/studiengang'))"
@@ -1430,6 +1457,9 @@ export const AbgabetoolAssistenz = {
</template> </template>
</Dropdown> </Dropdown>
</div> </div>
<div class="col-auto">
<label class="col-form-label">{{$capitalize($p.t('lehre/note'))}}:</label>
</div>
<div class="col-3"> <div class="col-3">
<Dropdown <Dropdown
:placeholder="$p.t('lehre/note')" :placeholder="$p.t('lehre/note')"
@@ -667,8 +667,10 @@ export const AbgabetoolMitarbeiter = {
setDetailComponent(details){ setDetailComponent(details){
this.loading=true this.loading=true
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id) const projektarbeiten = this.projektarbeiten?.retval ?? this.projektarbeiten
const pa = projektarbeiten.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
let paIsBenotet = false let paIsBenotet = false
if(pa.note !== undefined && pa.note !== null) { if(pa.note !== undefined && pa.note !== null) {
// check if the note is not defined as a non final projektarbeit note // check if the note is not defined as a non final projektarbeit note
+66 -34
View File
@@ -3,15 +3,20 @@ import DashboardAdminEdit from "./Admin/Edit.js";
import DashboardAdminWidgets from "./Admin/Widgets.js"; import DashboardAdminWidgets from "./Admin/Widgets.js";
import DashboardAdminPresets from "./Admin/Presets.js"; import DashboardAdminPresets from "./Admin/Presets.js";
import ApiDashboardBoard from "../../api/factory/dashboard/board.js";
import ApiDashboardWidget from "../../api/factory/dashboard/widget.js";
export default { export default {
name: 'DashboardAdmin',
components: { components: {
DashboardAdminEdit, DashboardAdminEdit,
DashboardAdminWidgets, DashboardAdminWidgets,
DashboardAdminPresets DashboardAdminPresets,
}, },
provide() { provide() {
return { return {
adminMode: true adminMode: true,
widgetsSetup: Vue.computed(() => this.dashboards[this.current] ? this.dashboards[this.current].widgetSetup : null)
}; };
}, },
data() { data() {
@@ -22,9 +27,6 @@ export default {
}; };
}, },
computed: { computed: {
apiurl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
},
dashboard() { dashboard() {
return this.dashboards.find(el => el.dashboard_id == this.current); return this.dashboards.find(el => el.dashboard_id == this.current);
} }
@@ -35,33 +37,50 @@ export default {
BsPrompt.popup('New Dashboard name').then( BsPrompt.popup('New Dashboard name').then(
name => { name => {
_name = name; _name = name;
return axios.post(this.apiurl + '/Dashboard/create', { const params = {
dashboard_kurzbz: name dashboard_kurzbz: name
}) };
} return this.$api
).then(res => { .call(ApiDashboardBoard.add(params))
let newDashboard = { .then(response =>{
dashboard_id: res.data.retval, this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
dashboard_kurzbz: _name,
beschreibung: '' let newDashboard = {
}; dashboard_id: response.data,
this.dashboards.push(newDashboard); dashboard_kurzbz: _name,
this.current = newDashboard.dashboard_id; beschreibung: ''
}).catch(err => err !== undefined ? console.error('ERROR:', err) : 0); };
this.dashboards.push(newDashboard);
this.current = newDashboard.dashboard_id;
})
.catch(this.$fhcAlert.handleSystemError);
});
}, },
dashboardUpdate(dashboard) { dashboardUpdate(dashboard) {
// TODO(chris): Loading or message return this.$api
axios.post(this.apiurl + '/Dashboard/update', dashboard).then(() => { .call(ApiDashboardBoard.update(dashboard))
let old = this.dashboards.find(el => el.dashboard_id == dashboard.dashboard_id); .then(response =>{
old.dashboard_kurzbz = dashboard.dashboard_kurzbz;
old.beschreibung = dashboard.beschreibung; this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
}).catch(err => console.error('ERROR:', err));
let old = this.dashboards.find(el => el.dashboard_id == dashboard.dashboard_id);
old.dashboard_kurzbz = dashboard.dashboard_kurzbz;
old.beschreibung = dashboard.beschreibung;
})
.catch(this.$fhcAlert.handleSystemError);
}, },
dashboardDelete(dashboard_id) { dashboardDelete(dashboard_id) {
axios.post(this.apiurl + '/Dashboard/delete', {dashboard_id}).then(() => { return this.$api
this.current = -1; .call(ApiDashboardBoard.delete(dashboard_id))
this.dashboards = this.dashboards.filter(el => el.dashboard_id != dashboard_id); .then(response => {
}).catch(err => console.error('ERROR:', err)); this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete'));
})
.catch(this.$fhcAlert.handleSystemError)
.finally(() => {
this.current = -1;
this.dashboards = this.dashboards.filter(el => el.dashboard_id != dashboard_id);
});
}, },
assignWidgets(widgets) { assignWidgets(widgets) {
this.widgets = widgets; this.widgets = widgets;
@@ -72,22 +91,35 @@ export default {
} }
}, },
created() { created() {
axios.get(this.apiurl + '/Dashboard').then(res => { this.$api
this.dashboards = res.data.retval; .call(ApiDashboardBoard.list())
}).catch(err => console.error('ERROR:', err)); .then(result => {
this.dashboards = result.data.retval;
for (const dashboard of this.dashboards) {
this.$api
.call(ApiDashboardWidget.list(dashboard.dashboard_id))
.then(res => {
dashboard.widgetSetup = res.data;
})
.catch(this.$fhcAlert.handleSystemError);
}
})
.catch(this.$fhcAlert.handleSystemError);
}, },
template: `<div class="dashboard-admin"> template: `<div class="dashboard-admin">
<div class="input-group"> <div class="input-group">
<label for="dashbaord-select" class="input-group-text">Dashboard:</label> <label for="dashboard-select" class="input-group-text">Dashboard:</label>
<select id="dashbaord-select" class="form-select" v-model="current"> <select id="dashboard-select" class="form-select" v-model="current">
<option v-for="dashboard in dashboards" :key="dashboard.dashboard_id" :value="dashboard.dashboard_id">{{dashboard.dashboard_kurzbz}}</option> <option v-for="dashboard in dashboards" :key="dashboard.dashboard_id" :value="dashboard.dashboard_id">{{dashboard.dashboard_kurzbz}}</option>
</select> </select>
<button class="btn btn-outline-secondary" type="button" @click="dashboardAdd"><i class="fa-solid fa-plus"></i></button> <button class="btn btn-outline-secondary" type="button" @click="dashboardAdd"><i class="fa-solid fa-plus"></i></button>
</div> </div>
<div v-if="dashboard"> <div v-if="dashboard">
<ul class="nav nav-tabs mt-3" role="tablist"> <ul class="nav nav-tabs mt-3" role="tablist">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="edit-tab" data-bs-toggle="tab" data-bs-target="#edit" type="button" role="tab" aria-controls="edit" aria-selected="false">Edit</button> <button class="nav-link" id="edit-tab" data-bs-toggle="tab" data-bs-target="#edit" type="button" role="tab" aria-controls="edit" aria-selected="false">{{this.$p.t('ui', 'bearbeiten')}}</button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link active" id="widgets-tab" data-bs-toggle="tab" data-bs-target="#widgets" type="button" role="tab" aria-controls="widgets" aria-selected="true">Widgets</button> <button class="nav-link active" id="widgets-tab" data-bs-toggle="tab" data-bs-target="#widgets" type="button" role="tab" aria-controls="widgets" aria-selected="true">Widgets</button>
@@ -101,7 +133,7 @@ export default {
<dashboard-admin-edit v-bind="dashboard" :key="dashboard.dashboard_id" @change="dashboardUpdate($event)" @delete="dashboardDelete($event)"></dashboard-admin-edit> <dashboard-admin-edit v-bind="dashboard" :key="dashboard.dashboard_id" @change="dashboardUpdate($event)" @delete="dashboardDelete($event)"></dashboard-admin-edit>
</div> </div>
<div class="tab-pane fade show active" id="widgets" role="tabpanel" aria-labelledby="widgets-tab"> <div class="tab-pane fade show active" id="widgets" role="tabpanel" aria-labelledby="widgets-tab">
<dashboard-admin-widgets :key="dashboard.dashboard_id" :dashboard_id="dashboard.dashboard_id" :widgets="widgets" @change="test" @assign-widgets="assignWidgets"></dashboard-admin-widgets> <dashboard-admin-widgets :key="dashboard.dashboard_id" :dashboard_id="dashboard.dashboard_id" :widgets="widgets" @assign-widgets="assignWidgets"></dashboard-admin-widgets>
</div> </div>
<div class="tab-pane fade" id="presets" role="tabpanel" aria-labelledby="presets-tab"> <div class="tab-pane fade" id="presets" role="tabpanel" aria-labelledby="presets-tab">
<dashboard-admin-presets :dashboard="dashboard.dashboard_kurzbz" :widgets="widgets"></dashboard-admin-presets> <dashboard-admin-presets :dashboard="dashboard.dashboard_kurzbz" :widgets="widgets"></dashboard-admin-presets>
+4 -3
View File
@@ -18,7 +18,8 @@ export default {
}, },
methods: { methods: {
sendDelete() { sendDelete() {
BsConfirm.popup('Sure?').then(() => this.$emit('delete', this.dashboard_id)).catch(); BsConfirm.popup(this.$p.t('ui', 'confirm_delete') + " " + this.$p.t('ui', 'deleteInfo'))
.then(() => this.$emit('delete', this.dashboard_id)).catch();
} }
}, },
template: `<div class="dashboard-admin-edit px-3"> template: `<div class="dashboard-admin-edit px-3">
@@ -31,8 +32,8 @@ export default {
<textarea id="dashboard-admin-edit-beschreibung" class="form-control" v-model="desc"></textarea> <textarea id="dashboard-admin-edit-beschreibung" class="form-control" v-model="desc"></textarea>
</div> </div>
<div> <div>
<button class="btn btn-danger" @click="sendDelete">Delete</button> <button class="btn btn-danger" @click="sendDelete">{{this.$p.t('ui', 'loeschen')}}</button>
<button class="btn btn-primary" @click="$emit('change', {dashboard_id,dashboard_kurzbz:kurzbz,beschreibung:desc})">Update</button> <button class="btn btn-primary" @click="$emit('change', {dashboard_id,dashboard_kurzbz:kurzbz,beschreibung:desc})">{{this.$p.t('ui', 'btnAktualisieren')}}</button>
</div> </div>
</div>` </div>`
} }
+111 -89
View File
@@ -1,6 +1,7 @@
import DashboardSection from "../Section.js"; import DashboardSection from "../Section.js";
import DashboardWidgetPicker from "../Widget/Picker.js"; import DashboardWidgetPicker from "../Widget/Picker.js";
import ObjectUtils from "../../../helpers/ObjectUtils.js"; import ObjectUtils from "../../../helpers/ObjectUtils.js";
import ApiDashboardPreset from "../../../api/factory/dashboard/preset.js";
export default { export default {
components: { components: {
@@ -17,9 +18,6 @@ export default {
tmpLoading: '' tmpLoading: ''
}), }),
computed: { computed: {
apiurl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
},
pickerWidgets() { pickerWidgets() {
return this.widgets.filter(widget => widget.allowed); return this.widgets.filter(widget => widget.allowed);
} }
@@ -28,6 +26,7 @@ export default {
widgetAdd(section_name, widget) { widgetAdd(section_name, widget) {
this.$refs.widgetpicker.getWidget().then(widget_id => { this.$refs.widgetpicker.getWidget().then(widget_id => {
widget.widget = widget_id; widget.widget = widget_id;
widget.id = 'loading_' + String((new Date()).valueOf());
delete widget.custom; delete widget.custom;
widget.preset = 1; widget.preset = 1;
let loading = {...widget}; let loading = {...widget};
@@ -36,130 +35,153 @@ export default {
if (section.name == section_name) if (section.name == section_name)
section.widgets.push(loading); section.widgets.push(loading);
}); });
axios.post(this.apiurl + '/Config/addWidgetsToPreset', { const params = {
db: this.dashboard, dashboard: this.dashboard,
funktion_kurzbz: section_name, funktion_kurzbz: section_name,
widgets: [widget] widget
}).then(result => { };
let newId = Object.keys(result.data.retval.data[section_name].widgets).pop();
widget.id = newId; return this.$api
widget.custom = 1; .call(ApiDashboardPreset.addWidget(params))
this.sections.forEach(section => { .then(result => {
if (section.name == section_name) { let newId = result.data;
section.widgets.splice(section.widgets.indexOf(loading),1); widget.id = newId;
section.widgets.push(widget); widget.custom = 1;
} this.sections.forEach(section => {
}); if (section.name == section_name) {
}).catch(error => { section.widgets.splice(section.widgets.indexOf(loading),1);
console.error('ERROR: ', error); section.widgets.push(widget);
alert('ERROR: ' + error.response.data.retval); }
}); });
}).catch(() => {}); this.funktionen.forEach(funktion => {
if(funktion.funktion_kurzbz === section_name && funktion.has_preset < 1) {
funktion.has_preset = 1;
}
});
})
.catch(this.$fhcAlert.handleSystemError);
})
.catch(() => {});
}, },
widgetUpdate(section_name, payload) { widgetUpdate(section_name, payload) {
payload = payload[section_name]; payload = payload[section_name];
for (var k in payload) { for (var k in payload) {
for (var i in this.sections) { const section = this.sections.find(section => section.name == section_name);
if (this.sections[i].name == section_name) { for (var wid in section.widgets) {
for (var wid in this.sections[i].widgets) { if (section.widgets[wid].id == k) {
if (this.sections[i].widgets[wid].id == k) { payload[k] = ObjectUtils.mergeDeep(section.widgets[wid], payload[k]);
payload[k] = ObjectUtils.mergeDeep(this.sections[i].widgets[wid], payload[k]); // NOTE(chris): remove internal props
// NOTE(chris): remove internal props for (var prop of ['_x', '_y', '_w', '_h', 'index', 'id'])
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1}) if (payload[k][prop])
if (payload[k][prop]) delete payload[k][prop];
delete payload[k][prop];
break;
}
}
break; break;
} }
} }
payload[k].widgetid = k; payload[k].widgetid = k;
delete payload[k].custom; delete payload[k].custom;
} }
axios.post(this.apiurl + '/Config/addWidgetsToPreset', { this.$api
db: this.dashboard, .call(Object.entries(payload).map(([key, widget]) => [
funktion_kurzbz: section_name, key,
widgets: payload ApiDashboardPreset.addWidget({
}).then(() => { dashboard: this.dashboard,
this.sections.forEach(section => { funktion_kurzbz: section_name,
if (section.name == section_name) { widget
section.widgets.forEach((widget, i) => { })
if (payload[widget.id]) { ]))
payload[widget.id].id = widget.id; .then(result => {
payload[widget.id].index = widget.index; this.sections.forEach(section => {
section.widgets[i] = payload[widget.id]; if (section.name == section_name) {
section.widgets[i].custom = 1; section.widgets.forEach((widget, i) => {
} if (payload[widget.id]) {
}); payload[widget.id].id = widget.id;
} payload[widget.id].index = widget.index;
}); section.widgets[i] = payload[widget.id];
}).catch(error => { section.widgets[i].custom = 1;
// TODO(chris): revert placement on failure }
console.error('ERROR: ', error); });
alert('ERROR: ' + error.response.data.retval); }
}); });
})
.catch(this.$fhcAlert.handleSystemError);
}, },
widgetRemove(section_name, id) { widgetRemove(section_name, id) {
axios.post(this.apiurl + '/Config/removeWidgetFromPreset', { const params = {
db: this.dashboard, db: this.dashboard,
funktion_kurzbz: section_name, funktion_kurzbz: section_name,
widgetid: id widgetid: id
}).then(() => { };
this.sections.forEach(section => { return this.$api
if (section.name == section_name) .call(ApiDashboardPreset.removeWidget(params))
section.widgets = section.widgets.filter(widget => widget.id != id); .then(result => {
}); this.sections.forEach(section => {
}).catch(error => { if (section.name == section_name)
console.error('ERROR: ', error); section.widgets = section.widgets.filter(widget => widget.id != id);
alert('ERROR: ' + error.response.data.retval); });
}); })
.catch(this.$fhcAlert.handleSystemError);
}, },
loadSections(evt) { loadSections(evt) {
let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value); let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value);
this.sections = []; this.sections = [];
this.tmpLoading = funktionen.join('###'); this.tmpLoading = funktionen.join('###');
axios.get(this.apiurl + '/Config/presetBatch', {params: {
const params = {
db: this.dashboard, db: this.dashboard,
funktionen funktionen
}}).then(res => { };
if (this.tmpLoading !== funktionen.join('###'))
return; // NOTE(chris): prevent race condition return this.$api
for (var section in res.data.retval) { .call(ApiDashboardPreset.getBatch(params))
let widgets = []; .then(result => {
for (var wid in res.data.retval[section]) { if (this.tmpLoading !== funktionen.join('###'))
res.data.retval[section][wid].id = wid; return; // NOTE(chris): prevent race condition
res.data.retval[section][wid].custom = 1; for (var section in result.data) {
widgets.push(res.data.retval[section][wid]); let widgets = [];
for (var wid in result.data[section]) {
result.data[section][wid].id = wid;
result.data[section][wid].custom = 1;
widgets.push(result.data[section][wid]);
}
this.sections.push({
name: section,
widgets
});
} }
this.sections.push({ })
name: section, .catch(this.$fhcAlert.handleSystemError);
widgets
}); },
} loadFunktionen() {
}).catch(err => console.error('ERROR:', err)); this.$api
.call(ApiDashboardPreset.list(this.dashboard))
.then(result => {
this.funktionen = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
} }
}, },
created() { created() {
axios.get(this.apiurl + '/Config/funktionen').then(res => { this.loadFunktionen();
this.funktionen = {general: 'GENERAL'};
res.data.retval.forEach(funktion => {
this.funktionen[funktion.funktion_kurzbz] = funktion.beschreibung;
});
}).catch(err => console.error('ERROR:', err));
}, },
watch: { watch: {
dashboard() { dashboard() {
// TODO(chris): this should be done without a watcher // TODO(chris): this should be done without a watcher
this.loadSections({target:this.$refs.funktionenList}); this.loadSections({target:this.$refs.funktionenList});
this.loadFunktionen();
} }
}, },
template: `<div class="dashboard-admin-presets"> template: `<div class="dashboard-admin-presets">
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
<select ref="funktionenList" style="height:30em" class="form-control" multiple @input="loadSections"> <select ref="funktionenList" style="height:30em" class="form-control" multiple @input="loadSections">
<option v-for="name,id in funktionen" :key="id" :value="id">{{ name }}</option> <option
v-for="funktion in funktionen"
:key="funktion.funktion_kurzbz"
:value="funktion.funktion_kurzbz"
:class="(funktion.has_preset > 0) ? 'fw-bold' : ''"
>{{ funktion.beschreibung }}</option>
</select> </select>
</div> </div>
<div class="col-9"> <div class="col-9">
+13 -20
View File
@@ -1,3 +1,5 @@
import ApiDashboardWidget from "../../../api/factory/dashboard/widget.js";
export default { export default {
emits: [ emits: [
"change", "change",
@@ -7,34 +9,25 @@ export default {
dashboard_id: Number, dashboard_id: Number,
widgets: Array widgets: Array
}, },
computed: {
apiurl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
}
},
methods: { methods: {
sendChange(widget_id) { sendChange(widget_id) {
let allow = !this.widgets.find(el => el.widget_id == widget_id).allowed; let allow = !this.widgets.find(el => el.widget_id == widget_id).allowed;
axios.post(this.apiurl + '/Widget/setAllowed', {
dashboard_id: this.dashboard_id, this.$api
widget_id, .call(ApiDashboardWidget.setAllowed(this.dashboard_id, widget_id, allow))
action: allow ? 'add' : 'delete' .catch(this.$fhcAlert.handleSystemError);
}).catch(err => console.error('ERROR: ' + err));
} }
}, },
created() { created() {
axios.get(this.apiurl + '/Widget/getAll', { this.$api
params:{ .call(ApiDashboardWidget.list(this.dashboard_id))
dashboard_id: this.dashboard_id .then(result => {
} this.$emit('assignWidgets', result.data.map(el => ({
}).then(
result => {
this.$emit('assignWidgets', result.data.retval.map(el => ({
...el, ...el,
...{setup:JSON.parse(el.setup),arguments:JSON.parse(el.arguments),allowed:!!el.allowed} allowed: !!el.allowed
}))); })));
} })
).catch(err => console.error('ERROR:', err)); .catch(this.$fhcAlert.handleSystemError);
}, },
template: ` template: `
<div class="dashboard-admin-widgets"> <div class="dashboard-admin-widgets">
+105 -138
View File
@@ -2,7 +2,8 @@ import DashboardSection from "./Section.js";
import DashboardWidgetPicker from "./Widget/Picker.js"; import DashboardWidgetPicker from "./Widget/Picker.js";
import ObjectUtils from "../../helpers/ObjectUtils.js"; import ObjectUtils from "../../helpers/ObjectUtils.js";
import ApiDashboard from '../../api/factory/cis/dashboard.js'; import ApiDashboardWidget from '../../api/factory/dashboard/widget.js';
import ApiDashboardUser from '../../api/factory/dashboard/user.js';
export default { export default {
name: 'Dashboard', name: 'Dashboard',
@@ -20,181 +21,147 @@ export default {
type: Object, type: Object,
required: true, required: true,
validator(value) { validator(value) {
return value && value.name && value.uid && value.timezone return value && value.name && value.timezone
} }
} }
}, },
data() { data() {
return { return {
sections: [], widgets: [],
widgets: null, originalWidgets: {},
editMode: false, widgetsSetup: null,
viewDataInternal: this.viewData editMode: false
} }
}, },
provide() { provide() {
return { return {
editMode: Vue.computed(()=>this.editMode), editMode: Vue.computed(()=>this.editMode),
widgetsSetup: Vue.computed(() => this.widgets), widgetsSetup: Vue.computed(() => this.widgetsSetup),
timezone: Vue.computed(() => this.viewData.timezone) timezone: Vue.computed(() => this.viewData.timezone)
} }
}, },
computed: {
apiurl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
}
},
methods: { methods: {
widgetAdd(section_name, widget) { widgetAdd(section_name, widget) {
if (this.widgets === null) { // TODO(chris): remove section_name? (change order of params => get rid of it)
axios.get(this.apiurl + '/Widget/getWidgetsForDashboard', {params:{ this.$refs.widgetpicker
db: this.dashboard .getWidget()
}}).then(res => { .then(widget_id => {
res.data.retval.forEach(widget => { widget.widget = widget_id;
widget.arguments = JSON.parse(widget.arguments); widget.id = 'loading_' + String((new Date()).valueOf());
widget.setup = JSON.parse(widget.setup); let loading = { ...widget };
}); loading.loading = true;
this.widgets = res.data.retval; this.widgets.push(loading);
}).catch(err => console.error('ERROR:', err));
} this.$api
this.$refs.widgetpicker.getWidget().then(widget_id => { .call(ApiDashboardUser.addWidget(this.dashboard, widget))
widget.widget = widget_id; .then(result => {
widget.id = 'loading_' + String((new Date()).valueOf()); widget.id = result.data;
let loading = {...widget}; this.widgets.splice(this.widgets.indexOf(loading), 1);
loading.loading = true; this.widgets.push(widget);
this.sections.forEach(section => { this.originalWidgets[widget.id] = structuredClone(ObjectUtils.deepToRaw(widget));
if (section.name == section_name) })
section.widgets.push(loading); .catch(this.$fhcAlert.handleSystemError);
}); })
.catch(() => {});
axios.post(this.apiurl + '/Config/addWidgetsToUserOverride', {
db: this.dashboard,
funktion_kurzbz: section_name,
widgets: [widget]
}).then(result => {
let newId = Object.keys(result.data.retval.data[section_name].widgets).pop();
widget.id = newId;
this.sections.forEach(section => {
if (section.name == section_name) {
section.widgets.splice(section.widgets.indexOf(loading),1);
section.widgets.push(widget);
}
});
}).catch(error => {
console.error('ERROR: ', error);
alert('ERROR: ' + error.response.data.retval);
});
}).catch(() => {});
}, },
widgetUpdate(section_name, payload) { widgetUpdate(section_name, payload) {
payload = payload[section_name]; payload = payload[section_name];
for (var k in payload) { for (var k in payload) {
for (var i in this.sections) { for (var wid in this.widgets) {
if (this.sections[i].name == section_name) { if (this.widgets[wid].id == k) {
for (var wid in this.sections[i].widgets) { payload[k] = ObjectUtils.mergeDeep(this.widgets[wid], payload[k]);
if (this.sections[i].widgets[wid].id == k) { // NOTE(chris): remove internal props
payload[k] = ObjectUtils.mergeDeep(this.sections[i].widgets[wid], payload[k]); for (var prop of ['_x','_y','_w','_h','index','id','preset'])
// NOTE(chris): remove internal props if (payload[k][prop])
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1,preset:1}) delete payload[k][prop];
if (payload[k][prop])
delete payload[k][prop];
break;
}
}
break; break;
} }
} }
payload[k].widgetid = k; payload[k].widgetid = k;
} }
axios.post(this.apiurl + '/Config/addWidgetsToUserOverride', { this.$api
db: this.dashboard, .call(Object.entries(payload).map(([key, widget]) => [
funktion_kurzbz: section_name, key,
widgets: payload ApiDashboardUser.addWidget(this.dashboard, widget)
}).then(() => { ]))
this.sections.forEach(section => { .then(result => {
if (section.name == section_name) { const failed = result
section.widgets.forEach((widget, i) => { .filter(o => o.status == 'rejected')
if (payload[widget.id]) { .map(o => o.reason.config.errorHeader);
payload[widget.id].id = widget.id;
payload[widget.id].index = widget.index; this.widgets.forEach((widget, i) => {
section.widgets[i] = payload[widget.id]; if (failed.includes(widget.id)) {
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
/** NOTE(chris): if you wanna hide or unhide a
* preset and it fails: switch around the hidden
* value to revert it properly (checkboxes can't
* really handle it otherwise)
*/
if (payload[widget.id].hidden !== undefined) {
this.widgets[i].hidden = payload[widget.id].hidden;
this.$nextTick(() => {
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
});
} }
}); } else if (payload[widget.id]) {
} payload[widget.id].id = widget.id;
}); payload[widget.id].index = widget.index;
}).catch(error => { this.widgets[i] = payload[widget.id];
// TODO(chris): revert placement on failure this.originalWidgets[widget.id] = structuredClone(ObjectUtils.deepToRaw(this.widgets[i]));
console.error('ERROR: ', error); }
alert('ERROR: ' + error.response.data.retval); });
}); })
.catch(this.$fhcAlert.handleSystemError);
}, },
widgetRemove(section_name, id) { widgetRemove(section_name, id) {
axios.post(this.apiurl + '/Config/removeWidgetFromUserOverride', { this.$api
db: this.dashboard, .call(ApiDashboardUser.removeWidget(this.dashboard, id))
funktion_kurzbz: section_name, .then(() => {
widgetid: id this.widgets = this.widgets.filter(widget => widget.id != id);
}).then(() => { })
this.sections.forEach(section => { .catch(this.$fhcAlert.handleSystemError);
if (section.name == section_name)
section.widgets = section.widgets.filter(widget => widget.id != id);
});
}).catch(error => {
console.error('ERROR: ', error);
alert('ERROR: ' + error.response.data.retval);
});
} }
}, },
created() { created() {
this.$p.loadCategory('dashboard'); this.$p.loadCategory('dashboard');
axios.get(this.apiurl + '/Widget/getWidgetsForDashboard', {
params: {
db: this.dashboard
}
}).then(res => {
this.widgets = res.data.retval;
}).catch(err => console.error('ERROR:', err));
axios.get(this.apiurl + '/Config', {params:{ this.$api
db: this.dashboard .call(ApiDashboardWidget.listAllowed(this.dashboard))
}}).then(res => { .then(res => {
for (var name in res.data.retval) { this.widgetsSetup = res.data;
let widgets = []; })
let remove = []; .catch(this.$fhcAlert.handleSystemError);
for (var wid in res.data.retval[name].widgets) {
res.data.retval[name].widgets[wid].id = wid; this.$api
if (res.data.retval[name].widgets[wid].custom || res.data.retval[name].widgets[wid].preset) .call(ApiDashboardUser.get(this.dashboard))
widgets.push(res.data.retval[name].widgets[wid]); .then(res => {
else const widgets = [];
const remove = [];
for (var wid in res.data.general.widgets) {
let widget = res.data.general.widgets[wid];
widget.id = wid;
if (widget.custom || widget.preset) {
widgets.push(widget);
this.originalWidgets[wid] = structuredClone(widget);
} else {
remove.push(wid); remove.push(wid);
}
} }
this.sections.push({
name: name, remove.forEach(wid => this.widgetRemove('general', wid));
widgets: widgets
}); this.widgets = widgets;
remove.forEach(wid => this.widgetRemove(name, wid)); })
} .catch(this.$fhcAlert.handleSystemError);
this.sections = this.sections.sort((section1, section2) => {
if(section1.name == 'custom')
return 1;
if (section2.name == 'custom')
return -1;
return section2.widgets.length - section1.widgets.length;
});
}).catch(err => console.error('ERROR:', err));
},
async beforeMount() {
if (!this.viewData.name || !this.viewData.uid) {
const res = await this.$api.call(ApiDashboard.getViewData());
this.viewDataInternal = res.data
}
}, },
template: ` template: `
<div class="core-dashboard"> <div class="core-dashboard">
<h3 v-show="viewDataInternal?.name"> <h3>
{{ $p.t('global/personalGreeting', [ viewDataInternal?.name ]) }} {{ $p.t('global/personalGreeting', [ viewData?.name ]) }}
<button style="margin-left: 8px;" class="btn" @click="editMode = !editMode" aria-label="edit dashboard" v-tooltip="{showDelay:1000,value:'edit dashboard'}"><i class="fa-solid fa-gear" aria-hidden="true"></i></button> <button style="margin-left: 8px;" class="btn" @click="editMode = !editMode" aria-label="edit dashboard" v-tooltip="{showDelay:1000,value:'edit dashboard'}"><i class="fa-solid fa-gear" aria-hidden="true"></i></button>
</h3> </h3>
<dashboard-section v-for="(section, index) in sections" :key="section.name" :seperator="index" :name="section.name" :widgets="section.widgets" @widgetAdd="widgetAdd" @widgetUpdate="widgetUpdate" @widgetRemove="widgetRemove"></dashboard-section> <dashboard-section :seperator="0" name="general" :widgets="widgets" @widgetAdd="widgetAdd" @widgetUpdate="widgetUpdate" @widgetRemove="widgetRemove"></dashboard-section>
<dashboard-widget-picker ref="widgetpicker" :widgets="widgets"></dashboard-widget-picker> <dashboard-widget-picker ref="widgetpicker" :widgets="widgetsSetup"></dashboard-widget-picker>
</div>` </div>`
} }
+17 -3
View File
@@ -1,5 +1,5 @@
import BsModal from "../Bootstrap/Modal.js"; import BsModal from "../Bootstrap/Modal.js";
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js"; import { useCachedWidgetLoader } from "../../composables/Dashboard/CachedWidgetLoader.js";
import HeightTransition from "../Tranistion/HeightTransition.js"; import HeightTransition from "../Tranistion/HeightTransition.js";
export default { export default {
@@ -70,6 +70,14 @@ export default {
ready() { ready() {
return this.component && this.arguments !== null; return this.component && this.arguments !== null;
}, },
visible: {
get() {
return !this.hidden;
},
set(value) {
this.$emit('remove', this.hidden);
}
}
}, },
methods: { methods: {
unpin(){ unpin(){
@@ -142,8 +150,14 @@ export default {
this.isLoading = false; this.isLoading = false;
}, },
}, },
setup() {
const { actions } = useCachedWidgetLoader();
return {
loadWidget: actions.load
};
},
async created() { async created() {
this.widget = await CachedWidgetLoader.loadWidget(this.id); this.widget = await this.loadWidget(this.id);
let component = (await import(this.widget.setup.file)).default; let component = (await import(this.widget.setup.file)).default;
this.$options.components["widget" + this.widget.widget_id] = component; this.$options.components["widget" + this.widget.widget_id] = component;
this.component = "widget" + this.widget.widget_id; this.component = "widget" + this.widget.widget_id;
@@ -185,7 +199,7 @@ export default {
</a> </a>
<Transition> <Transition>
<div v-if="!custom && editMode" class="col-auto px-1 form-switch"> <div v-if="!custom && editMode" class="col-auto px-1 form-switch">
<input class="form-check-input ms-0" type="checkbox" role="switch" aria-label="toggle widget" id="flexSwitchCheckChecked" :checked="!hidden" @input="$emit('remove', hidden)"> <input class="form-check-input ms-0" type="checkbox" role="switch" aria-label="toggle widget" id="flexSwitchCheckChecked" v-model="visible" :value="true">
</div> </div>
</Transition> </Transition>
</div> </div>
+14 -7
View File
@@ -1,7 +1,7 @@
import BsConfirm from "../Bootstrap/Confirm.js"; import BsConfirm from "../Bootstrap/Confirm.js";
import DropGrid from '../Drop/Grid.js' import DropGrid from '../Drop/Grid.js'
import DashboardItem from "./Item.js"; import DashboardItem from "./Item.js";
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js"; import { useCachedWidgetLoader } from "../../composables/Dashboard/CachedWidgetLoader.js";
import WidgetIcon from "./Widget/WidgetIcon.js" import WidgetIcon from "./Widget/WidgetIcon.js"
export default { export default {
@@ -125,23 +125,23 @@ export default {
}, },
checkResizeLimit(item, w, h) { checkResizeLimit(item, w, h) {
// NOTE(chris): widgets needs to be loaded for this to work // NOTE(chris): widgets needs to be loaded for this to work
let widget = CachedWidgetLoader.getWidget(item.widget); let widget = this.widgetState[item.widget];
if (widget) { if (widget) {
let minmaxW = widget.setup.width; let minmaxW = { ...widget.setup.width };
if (minmaxW.max) if (minmaxW.max)
minmaxW.min = minmaxW.min || 1; minmaxW.min = minmaxW.min || 1;
else else
minmaxW = {min:minmaxW,max:minmaxW}; minmaxW = { min: minmaxW, max: minmaxW };
if (w < minmaxW.min) if (w < minmaxW.min)
w = minmaxW.min; w = minmaxW.min;
if (w > minmaxW.max) if (w > minmaxW.max)
w = minmaxW.max; w = minmaxW.max;
let minmaxH = widget.setup.height; let minmaxH = { ...widget.setup.height };
if (minmaxH.max) if (minmaxH.max)
minmaxH.min = minmaxH.min || 1; minmaxH.min = minmaxH.min || 1;
else else
minmaxH = {min:minmaxH,max:minmaxH}; minmaxH = { min: minmaxH, max: minmaxH };
if (h < minmaxH.min) if (h < minmaxH.min)
h = minmaxH.min; h = minmaxH.min;
if (h > minmaxH.max) if (h > minmaxH.max)
@@ -151,7 +151,7 @@ export default {
}, },
removeWidget(item, revert) { removeWidget(item, revert) {
if (item.custom) { if (item.custom) {
BsConfirm.popup('Are you sure you want to delete this widget?').then(() => this.$emit('widgetRemove', this.name, item.id)); BsConfirm.popup(this.$p.t('dashboard', 'alert_deleteWidget')).then(() => this.$emit('widgetRemove', this.name, item.id));
} else { } else {
let update = {}; let update = {};
update[item.id] = { hidden: !revert }; update[item.id] = { hidden: !revert };
@@ -199,6 +199,13 @@ export default {
this.$emit('widgetUpdate', this.name, payload); this.$emit('widgetUpdate', this.name, payload);
} }
}, },
setup() {
const { state: widgetState } = useCachedWidgetLoader();
return {
widgetState
};
},
mounted() { mounted() {
let self = this; let self = this;
let cont = self.$refs.container; let cont = self.$refs.container;
+4 -3
View File
@@ -170,6 +170,7 @@ export default {
return this.$attrs.modelValue; return this.$attrs.modelValue;
}, },
set(v) { set(v) {
this.clearValidationForThisName()
if (!this.$attrs.hasOwnProperty('modelValue')) if (!this.$attrs.hasOwnProperty('modelValue'))
this.modelValueDummy = v; this.modelValueDummy = v;
this.$emit('update:modelValue', v); this.$emit('update:modelValue', v);
@@ -242,9 +243,9 @@ export default {
template: ` template: `
<component :is="!hasContainer ? 'FhcFragment' : 'div'" class="position-relative" :class="autoContainerClass"> <component :is="!hasContainer ? 'FhcFragment' : 'div'" class="position-relative" :class="autoContainerClass">
<label v-if="label && lcType != 'radio' && lcType != 'checkbox'" :class="!noAutoClass && 'form-label'" :for="idCmp">{{label}}</label> <label v-if="label && lcType != 'radio' && lcType != 'checkbox'" :class="!noAutoClass && 'form-label'" :for="idCmp">{{label}}</label>
<input v-if="tag == 'input'" :type="lcType" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)"> <input v-if="tag == 'input'" :type="lcType" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="$emit('input', $event)">
<textarea v-else-if="tag == 'textarea'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)"></textarea> <textarea v-else-if="tag == 'textarea'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="$emit('input', $event)"></textarea>
<select v-else-if="tag == 'select'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)"> <select v-else-if="tag == 'select'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="$emit('input', $event)">
<slot></slot> <slot></slot>
</select> </select>
<component <component
@@ -74,6 +74,16 @@ export default {
], ],
'abschlussdokument_lehrgaenge.xml.php': [ 'abschlussdokument_lehrgaenge.xml.php': [
'AbschlussdokumentLehrgaenge' 'AbschlussdokumentLehrgaenge'
],
'microcredential.xml.php' : [
'microcredentialzertifikat_1',
'microcredentialzertifikat_2',
'microcredentialzertifikat_3',
'microcredentialzertifikat_4',
'microcredential_1',
'microcredential_2',
'microcredential_3',
'microcredential_4',
] ]
}, },
documentDropdownObject: {} documentDropdownObject: {}
@@ -14,6 +14,9 @@ export default {
inject: { inject: {
defaultSemester: { defaultSemester: {
from: 'defaultSemester' from: 'defaultSemester'
},
currentSemester: {
from: 'currentSemester'
} }
}, },
computed: { computed: {
@@ -95,8 +98,9 @@ export default {
this.formData.themenbereich = null; this.formData.themenbereich = null;
this.formData.projekttyp_kurzbz = null; this.formData.projekttyp_kurzbz = null;
this.formData.firma = null; this.formData.firma = null;
this.formData.lehrveranstaltung_id = null; // dont reset these form fields for UX reasons
this.formData.lehreinheit_id = null; // this.formData.lehrveranstaltung_id = null;
// this.formData.lehreinheit_id = null;
this.formData.beginn = null; this.formData.beginn = null;
this.formData.ende = null; this.formData.ende = null;
this.formData.freigegeben = true; this.formData.freigegeben = true;
@@ -109,7 +113,7 @@ export default {
getFormData(newProjektarbeit, studiensemester_kurzbz, additional_lehrveranstaltung_id) { getFormData(newProjektarbeit, studiensemester_kurzbz, additional_lehrveranstaltung_id) {
this.additional_lehrveranstaltung_id = additional_lehrveranstaltung_id; this.additional_lehrveranstaltung_id = additional_lehrveranstaltung_id;
this.studiensemester = studiensemester_kurzbz || this.defaultSemester; this.studiensemester = studiensemester_kurzbz || this.currentSemester;
this.newProjektarbeit = newProjektarbeit; this.newProjektarbeit = newProjektarbeit;
this.$api this.$api
@@ -121,7 +121,6 @@ export default {
height: 'auto', height: 'auto',
minHeight: '100', minHeight: '100',
selectableRows: true, selectableRows: true,
selectableRows: 1,
index: 'betreuer_id', index: 'betreuer_id',
persistence:{ persistence:{
columns: true, //persist column layout columns: true, //persist column layout
@@ -71,7 +71,6 @@ export default {
:mitarbeiter_uid="this.mitarbeiter_uid" :mitarbeiter_uid="this.mitarbeiter_uid"
typeHeader="mitarbeiter" typeHeader="mitarbeiter"
:domain="config.domain" :domain="config.domain"
fotoEditable
@redirectToLeitung="handleSelection" @redirectToLeitung="handleSelection"
> >
<template #uid>{{tile_MaUid}}</template> <template #uid>{{tile_MaUid}}</template>
+2 -2
View File
@@ -280,7 +280,7 @@ export const CoreFilterCmpt = {
}); });
} }
if (tabulatorOptions.selectable || (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length)) if (tabulatorOptions.selectable || tabulatorOptions.selectableRows || (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length))
this.tabulatorHasSelector = true; this.tabulatorHasSelector = true;
if (this.idField) { if (this.idField) {
@@ -358,7 +358,7 @@ export const CoreFilterCmpt = {
} }
}, },
_updateTabulator() { _updateTabulator() {
this.tabulatorHasSelector = this.tabulatorOptions.selectable || this.filteredColumns.filter(el => el.formatter == 'rowSelection').length; this.tabulatorHasSelector = this.tabulatorOptions.selectable || this.tabulatorOptions.selectableRows || this.filteredColumns.filter(el => el.formatter == 'rowSelection').length;
this.tabulator.setColumns(this.filteredColumns); this.tabulator.setColumns(this.filteredColumns);
this.tabulator.setData(this.filteredData); this.tabulator.setData(this.filteredData);
this._setHeaderFilter() this._setHeaderFilter()
@@ -1,29 +1,36 @@
let __widgets = {}; import ApiWidget from "../../api/factory/dashboard/widget.js";
let __widgetsStarted = {};
let __path = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard/Widget';
export default { const promises = Vue.ref([]);
getWidget(id) { const stateRef = Vue.ref([]);
return __widgets[id]; const state = Vue.readonly(stateRef);
},
loadWidget(id) {
if (__widgets[id])
return Promise.resolve(__widgets[id]);
if (__widgetsStarted[id])
return __widgetsStarted[id];
if (!__path)
return Promise.reject('Widget could not be loaded because there is no path yet!');
__widgetsStarted[id] = new Promise((resolve, reject) => { export function useCachedWidgetLoader() {
axios.get(__path, {params:{id}}).then(res => { const $api = Vue.inject('$api');
__widgets[id] = res.data.retval; const $fhcAlert = Vue.inject('$fhcAlert');
__widgetsStarted[id] = undefined;
resolve(__widgets[id]); function load(id) {
}).catch(error => reject(error.response.data.retval.error)); if (state.value[id])
}); return Promise.resolve(state.value[id]);
return __widgetsStarted[id];
}, if (!promises.value[id])
setPath(path) { promises.value[id] = new Promise((resolve, reject) => {
__path = path; $api
.call(ApiWidget.get(id))
.then(res => {
stateRef.value[id] = res.data;
promises.value[id] = undefined;
resolve(state.value[id]);
})
.catch($fhcAlert.handleSystemError);
});
return promises.value[id];
} }
return {
state,
actions: {
load
}
};
} }
+70 -29
View File
@@ -1,31 +1,72 @@
export default { /**
/** * Performs a deep merge of objects and returns new object. Does not modify
* Performs a deep merge of objects and returns new object. Does not modify * objects (immutable) and merges arrays via concatenation.
* objects (immutable) and merges arrays via concatenation. *
* * @param {...object} objects - Objects to merge
* @param {...object} objects - Objects to merge * @returns {object} New object with merged key/values
* @returns {object} New object with merged key/values */
*/ function mergeDeep(...objects) {
mergeDeep(...objects) { const isObject = obj => obj && typeof obj === 'object';
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
return objects.reduce((prev, obj) => { Object.keys(obj).forEach(key => {
Object.keys(obj).forEach(key => { const pVal = prev[key];
const pVal = prev[key]; const oVal = obj[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = this.mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev; if (Array.isArray(pVal) && Array.isArray(oVal)) {
}, {}); prev[key] = pVal.concat(...oVal);
} }
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = this.mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
/**
* Extends VUEs toRaw() function to nested Proxies
* @see https://www.reddit.com/r/javascript/comments/10gzynk/deep_cloning_objects_in_javascript_the_modern_way/
*
* @param object sourceObj - Object to transform
* @returns object
*/
function deepToRaw(sourceObj) {
const objectIterator = input => {
if (Array.isArray(input))
return input.map(objectIterator);
if (Vue.isRef(input) || Vue.isReactive(input) || Vue.isProxy(input))
return objectIterator(Vue.toRaw(input));
if (input && typeof input === 'object') {
/** use custom handling of 'Date' objects to avoid data loss if treating it like any other object.
* reminder:
* typeof (new Date()) ==> 'object'
* Object.keys(new Date()) ==> []
*/
if (input instanceof Date)
return input;
return Object.keys(input).reduce((acc, key) => {
acc[key] = objectIterator(input[key]);
return acc;
}, {});
}
return input;
};
return objectIterator(sourceObj);
}
export {
mergeDeep,
deepToRaw
}
export default {
mergeDeep,
deepToRaw
} }
+8 -8
View File
@@ -430,16 +430,16 @@ export default {
fhcApiAxios.interceptors.response.use( fhcApiAxios.interceptors.response.use(
response => { response => {
if (response.config?.errorHandling == 'off' const errorConfig = get_error_handler(response.config);
|| response.config?.errorHandling === false
|| response.config?.errorHandling == 'fail') if (!errorConfig.success)
return clean_return_value(response); return clean_return_value(response);
// NOTE(chris): loop through errors const errors = popHandleableErrors(errorConfig, response.data.errors);
if (response.data.errors)
response.data.errors = response.data.errors.filter( for (var type in errors) {
err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, response.config) errorConfig.handler[type](errors[type]);
); }
return clean_return_value(response); return clean_return_value(response);
}, },
+1
View File
@@ -92,6 +92,7 @@ require_once('dbupdate_3.4/68744_StV_settings.php');
require_once('dbupdate_3.4/62889_reihungstest_ueberwachung_mit_constructor.php'); require_once('dbupdate_3.4/62889_reihungstest_ueberwachung_mit_constructor.php');
require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php'); require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php');
require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php'); require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php');
require_once('dbupdate_3.4/71566_studienordnungsdokument_neuer_organisationseinheitstyp_programm.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>'; echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -0,0 +1,15 @@
<?php
// neuen Organisationseinheittyp (Programm) als Zeile hinzufügen
if($result = @$db->db_query("SELECT 1 FROM public.tbl_organisationseinheittyp WHERE organisationseinheittyp_kurzbz= 'Programm';"))
{
if($db->db_num_rows($result) == 0)
{
$qry = "INSERT INTO public.tbl_organisationseinheittyp(organisationseinheittyp_kurzbz, beschreibung, bezeichnung) VALUES ('Programm', 'Programm', 'Programm');";
if(!$db->db_query($qry))
echo '<strong>public.tbl_organisationseinheittyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>public.tbl_organisationseinheittyp: Zeile Programm hinzugefuegt!<br>';
}
}
+333 -4
View File
@@ -3903,6 +3903,172 @@ $phrases = array(
) )
) )
), ),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'geplZeitraum',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'geplanter Zeitraum',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'planned Period',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'bitteAuswaehlen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bitte auswählen...',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Please select...',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'hinweisLehrende',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Hinweis für Lehrende',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Note for Lecturers',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'lehreinheiten',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Lehreinheiten',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Teaching Units',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'lead',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Leitung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Lead',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'teamlead',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Team / Leitung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Team / Lead',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'ausblick_lvplanung',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Ausblick auf Ihre mögliche LV-Planung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Preview of Your Potential Course Planning',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'detailselfoverview',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => '<b>Achtung:</b> die vorliegenden Informationen stellen eine Vorabplanung dar und sind als Anfrage an Sie gedacht. <br /><br />
Die Beauftragung der tatsächlichen Lehrveranstaltungen erfolgt durch Ihre Kompetenzfeldleitung. <br /><br />
Ihre aktuell gültigen Lehraufträge und den LV Plan des aktuellen Semesters (Termine) finden Sie wie gewohnt unter „mein CIS“ -> „LV-Plan Hauptmenü“ bzw. „Lehrauftragsverwaltung“.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => '<b>Please note:</b> The information provided represents a preliminary planning and is intended as an inquiry to you.<br /><br />
The official assignment of the actual courses will be carried out by your Competence Field Manager.<br /><br />
Your currently valid teaching assignments and the course schedule for the current semester (dates) can be found as usual under “My CIS” “Schedule Main Menu” or “Teaching Assignment Administration”',
'description' => '',
'insertvon' => 'system'
)
)
),
array( array(
'app' => 'pep', 'app' => 'pep',
'category' => 'ui', 'category' => 'ui',
@@ -44726,9 +44892,9 @@ array(
'phrases' => array( 'phrases' => array(
array( array(
'sprache' => 'German', 'sprache' => 'German',
'text' => "Für den gesamten Studiengang verbindlicher Termin. 'text' => "Für den gesamten Studiengang verbindlicher Termin.
Liegt ein Termin in der Vergangenheit, kann nichts mehr hochgeladen werden. Ist es dennoch erforderlich, Liegt ein Termin in der Vergangenheit, kann nichts mehr hochgeladen werden. Ist es dennoch erforderlich,
haben Studierende bei der Studiengangsassistenz um eine Korrektur dieses Termins anzusuchen.", haben Studierende bei der Studiengangsassistenz um eine Korrektur dieses Termins anzusuchen.",
'description' => '', 'description' => '',
'insertvon' => 'system' 'insertvon' => 'system'
@@ -44919,7 +45085,7 @@ array(
array( array(
'sprache' => 'German', 'sprache' => 'German',
'text' => "Verspätete Projektabgabe ist bei Terminen, welche von der Studiengangsassistenz für den gesamten Studiengang fixiert wurden nicht erlaubt! 'text' => "Verspätete Projektabgabe ist bei Terminen, welche von der Studiengangsassistenz für den gesamten Studiengang fixiert wurden nicht erlaubt!
Um einen Endupload durchführen zu können, müssen Sie ein positiv benotetes Quality Gate 1 & Quality Gate 2 in der relevanten Projektarbeit absolviert haben.", Um einen Endupload durchführen zu können, müssen Sie ein positiv benotetes Quality Gate 1 & Quality Gate 2 in der relevanten Projektarbeit absolviert haben.",
'description' => '', 'description' => '',
'insertvon' => 'system' 'insertvon' => 'system'
@@ -45234,6 +45400,46 @@ array(
) )
) )
), ),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4downloadLatestAbgabe',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Zuletzt getätigte Abgabe herunterladen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Download latest uploaded File',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4termineTimeLine',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zeitstrahl Termine',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Timeline Deadlines',
'description' => '',
'insertvon' => 'system'
)
)
),
array( array(
'app' => 'core', 'app' => 'core',
'category' => 'abgabetool', 'category' => 'abgabetool',
@@ -46695,6 +46901,26 @@ array(
) )
) )
), ),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4noZuordnungBetreuerStudent',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Keine Zuordnung oder Berechtigung für die Projektarbeit gefunden!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No assignment or authorization found for the project!',
'description' => '',
'insertvon' => 'system'
)
)
),
// ABGABETOOL PHRASEN END // ABGABETOOL PHRASEN END
array( array(
'app' => 'core', 'app' => 'core',
@@ -56948,6 +57174,109 @@ I have been informed that I am under no obligation to consent to the transmissio
) )
), ),
// ### Refactor Messages END // ### Refactor Messages END
//
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'error_noLehrverbandAssigned',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'StudentIn ist in diesem Semester keinem Lehrverband zugeteilt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Student has no assignment to any teaching association',
'description' => '',
'insertvon' => 'system'
)
)
),
// ### Phrases Dashboard Admin START
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'deleteInfo',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Mit dieser Aktion werden auch alle Voreinstellungen der verbundenen Widgets gelöscht.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'This action will also delete all presets of the connected widgets.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'dashboard',
'phrase' => 'success_savePreset',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Voreinstellung erfolgreich aktualisiert',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Preset successfully updated',
'description' => '',
'insertvon' => 'system'
)
)
), array(
'app' => 'core',
'category' => 'dashboard',
'phrase' => 'alert_deleteWidget',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Sind Sie sicher, dass Sie dieses Widget löschen möchten?',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Are you sure you want to delete this widget?',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'confirm_delete',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Möchten Sie wirklich löschen?',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Do you really want to delete?',
'description' => '',
'insertvon' => 'system'
)
)
),
// ### Phrases Dashboard Admin END
); );
+16 -7
View File
@@ -875,7 +875,6 @@ function _getLehrecontainer($sws_proStg_arr)
$sws_proStg_arr = array_filter($sws_proStg_arr, function ($obj) { $sws_proStg_arr = array_filter($sws_proStg_arr, function ($obj) {
return return
!in_array($obj->studiengang_kz, BIS_EXCLUDE_STG) && !in_array($obj->studiengang_kz, BIS_EXCLUDE_STG) &&
$obj->studiengang_kz > 0 &&
$obj->studiengang_kz < 10000; $obj->studiengang_kz < 10000;
}); });
} }
@@ -886,13 +885,17 @@ function _getLehrecontainer($sws_proStg_arr)
{ {
$is_sommersemester = substr($sws_proStg->studiensemester_kurzbz, 0, 2) == 'SS'; $is_sommersemester = substr($sws_proStg->studiensemester_kurzbz, 0, 2) == 'SS';
$is_wintersemester = substr($sws_proStg->studiensemester_kurzbz, 0, 2) == 'WS'; $is_wintersemester = substr($sws_proStg->studiensemester_kurzbz, 0, 2) == 'WS';
$is_lehrgang = isset($sws_proStg->lgartcode);
$kennzeichen_name = $is_lehrgang ? 'LehrgangNr' : 'StgKz';
// Lehreobjekt generieren // Lehreobjekt generieren
if (empty($lehre_arr) || !lehre_stg_exists($sws_proStg->studiengang_kz, $lehre_arr)) if (empty($lehre_arr) || !lehre_stg_exists($sws_proStg->studiengang_kz, $lehre_arr))
{ {
$lehre_obj = new StdClass(); $lehre_obj = new StdClass();
$lehre_obj->StgKz = setLeadingZero(intval($sws_proStg->studiengang_kz), 4); $lehre_obj->{$kennzeichen_name} = $sws_proStg->melde_studiengang_kz;
//~ $lehre_obj->StgKz = setLeadingZero(intval($sws_proStg->studiengang_kz), 4);
$lehre_obj->SommersemesterSWS = $is_sommersemester ? $sws_proStg->sws : 0.00; $lehre_obj->SommersemesterSWS = $is_sommersemester ? $sws_proStg->sws : 0.00;
$lehre_obj->WintersemesterSWS = $is_wintersemester ? $sws_proStg->sws : 0.00; $lehre_obj->WintersemesterSWS = $is_wintersemester ? $sws_proStg->sws : 0.00;
@@ -1020,9 +1023,14 @@ function _generateXML($person_arr)
foreach ($person->lehre_arr as $lehre) foreach ($person->lehre_arr as $lehre)
{ {
$xml .= '<Lehre>'; $xml .= '<Lehre>';
$xml .= '<StgKz><![CDATA['. $lehre->StgKz. ']]></StgKz>';
$xml .= '<SommersemesterSWS><![CDATA['. $lehre->SommersemesterSWS. ']]></SommersemesterSWS>'; if (isset($lehre->LehrgangNr))
$xml .= '<WintersemesterSWS><![CDATA['. $lehre->WintersemesterSWS. ']]></WintersemesterSWS>'; $xml .= '<LehrgangNr><![CDATA['. $lehre->LehrgangNr. ']]></LehrgangNr>';
else
$xml .= '<StgKz><![CDATA['. $lehre->StgKz. ']]></StgKz>';
$xml .= '<SommersemesterSWS><![CDATA['. number_format($lehre->SommersemesterSWS, 2, '.', ''). ']]></SommersemesterSWS>';
$xml .= '<WintersemesterSWS><![CDATA['. number_format($lehre->WintersemesterSWS, 2, '.', ''). ']]></WintersemesterSWS>';
$xml .= '</Lehre>'; $xml .= '</Lehre>';
} }
@@ -1211,7 +1219,7 @@ function _outputHTML($person_arr)
{ {
echo ' echo '
<tr> <tr>
<td>'. $lehre->StgKz. '</td> <td>'. (isset($lehre->LehrgangNr) ? $lehre->LehrgangNr : $lehre->StgKz). '</td>
<td>'. $lehre->SommersemesterSWS. '</td> <td>'. $lehre->SommersemesterSWS. '</td>
<td>'. $lehre->WintersemesterSWS. '</td> <td>'. $lehre->WintersemesterSWS. '</td>
</tr>'; </tr>';
@@ -1359,7 +1367,8 @@ function lehre_stg_exists($studiengang_kz, $lehre_arr)
{ {
foreach($lehre_arr as $row) foreach($lehre_arr as $row)
{ {
if($row->StgKz == $studiengang_kz) $kennzeichenName = $row->LehrgangNr ?? $row->StgKz;
if(isset($row->{$kennzeichenName}) && $row->{$kennzeichenName} == $melde_studiengang_kz)
return true; return true;
} }
return false; return false;