Compare commits

..

2 Commits

Author SHA1 Message Date
ma0068 6324c3d467 add subquery for showing lvs of stundenplan in dependance of new parameter, deleting unused part of old parameter 2026-03-16 09:08:52 +01:00
ma0068 377c53cf60 add sql part 2026-03-12 14:12:53 +01:00
81 changed files with 1283 additions and 9602 deletions
+1 -8
View File
@@ -208,14 +208,7 @@ $config['navigation_header'] = array(
'expand' => true,
'sort' => 30,
'requiredPermissions' => 'lehre/anrechnungszeitfenster:rw'
),
'dashboardadmin' => array(
'link' => site_url('dashboard/Admin'),
'description' => 'Dashboard Admin',
'expand' => true,
'sort' => 40,
'requiredPermissions' => 'dashboard/admin:r'
)
)
)
)
)
-3
View File
@@ -64,9 +64,6 @@ $route['api/v1/system/[S|s]prache/(:any)'] = 'api/v1/system/sprache2/$1';
$route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1';
$route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1';
$route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1';
//Routes for Lvplan Verband and page for dropdown Stg/Semester/Verband/Gruppe
$route['Cis/StgOrgLvPlan/.*'] = 'Cis/StgOrgLvPlan/index/$1';
$route['Cis/OverviewLvPlan/.*'] = 'Cis/OverviewLvPlan/index/$1';
$route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz';
$route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1';
@@ -1,39 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class OverviewLvPlan extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r']
]);
// Load Config
$this->load->config('calendar');
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
$viewData = array(
'uid'=>getAuthUID(),
'timezone' => $this->config->item('timezone')
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'OverviewLvPlan']);
}
}
@@ -1,39 +0,0 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class StgOrgLvPlan extends Auth_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r']
]);
// Load Config
$this->load->config('calendar');
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
/**
* @return void
*/
public function index()
{
$viewData = array(
'uid'=>getAuthUID(),
'timezone' => $this->config->item('timezone')
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'StgOrgLvPlan']);
}
}
@@ -511,11 +511,10 @@ class Abgabe extends FHCAPI_Controller
return $projektarbeit->projektarbeit_id;
};
$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) {
@@ -847,10 +846,9 @@ class Abgabe extends FHCAPI_Controller
private function getProjektbetreuerEmailByProjektarbeitID($projektarbeit_id) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id);
if(count($result->retval) > 0) {
$email = getData($result);
return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email;
} else return '';
$email = $this->getDataOrTerminateWithError($result, 'general');
return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email;
}
@@ -208,6 +208,7 @@ class Documents extends FHCAPI_Controller
$this->load->model('system/Vorlage_model', 'VorlageModel');
$result = $this->VorlageModel->load($xsl);
$this->addMeta("ress", $result);
$vorlage = current($this->getDataOrTerminateWithError($result));
if (!$vorlage)
show_404();
@@ -220,7 +221,7 @@ class Documents extends FHCAPI_Controller
'gedruckt' => true,
'insertamum' => date('c'),
'insertvon' => getAuthUID(),
'uid' => $this->input->post_get('uid') ?: null,
'uid' => $this->input->post_get('uid') ?: '',
'archiv' => true,
'signiert' => !!$sign_user,
'stud_selfservice' => $vorlage->stud_selfservice
@@ -250,9 +251,6 @@ class Documents extends FHCAPI_Controller
'studiensemester_kurzbz' => $ss,
'student_uid' => $akteData['uid']
]);
if (!hasData($result)) $this->terminateWithError($this->p->t("stv", "error_noLehrverbandAssigned"));
$res = current($this->getDataOrTerminateWithError($result));
$studiengang_kz = $res->studiengang_kz;
@@ -334,7 +332,6 @@ class Documents extends FHCAPI_Controller
if ($prestudent_id) {
$this->load->model('crm/prestudent_model', 'PrestudentModel');
$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);
$prestudent = current($this->getDataOrTerminateWithError($result));
@@ -41,12 +41,7 @@ class LvPlan extends FHCAPI_Controller
'getLehreinheitStudiensemester' => self::PERM_LOGGED,
'studiensemesterDateInterval' => self::PERM_LOGGED,
'getLvPlanForStudiensemester' => self::PERM_LOGGED,
'getLv' => self::PERM_LOGGED,
'eventsStgOrg' => self::PERM_LOGGED,
'fetchFerienEvents' => self::PERM_LOGGED,
'getStudiengaenge' => self::PERM_LOGGED,
'getLehrverband' => self::PERM_LOGGED,
'getLv' => self::PERM_LOGGED
]);
$this->load->library('LogLib');
@@ -88,7 +83,7 @@ class LvPlan extends FHCAPI_Controller
// form validation
$this->form_validation->set_rules('start_date', "start_date", "required");
$this->form_validation->set_rules('end_date', "end_date", "required");
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
@@ -105,7 +100,7 @@ class LvPlan extends FHCAPI_Controller
// fetching ferien events
$ferienEvents = $this->fetchFerienEvents($start_date, $end_date);
$this->terminateWithSuccess(array_merge(
$lvplanEvents,
@@ -114,49 +109,6 @@ class LvPlan extends FHCAPI_Controller
));
}
/**
* fetches LvPlan for studiengang / semester / verband / gruppe
*
* @access public
*/
public function eventsStgOrg()
{
$this->load->library('StundenplanLib');
// form validation
$this->form_validation->set_rules('start_date', "start_date", "required");
$this->form_validation->set_rules('end_date', "end_date", "required");
//$this->form_validation->set_rules('stg_kz', "stg_kz", "required"); //no validation show empty calendar
if (!$this->form_validation->run())
{
$this->terminateWithValidationErrors($this->form_validation->error_array());
$stgOrgEvents = [];
$ferienEvents = [];
}
else
{
$start_date = $this->input->post('start_date', true);
$end_date = $this->input->post('end_date', true);
$stg_kz = $this->input->post('stg_kz', true);
$sem = $this->input->post('sem', true);
$verband = $this->input->post('verband', true);
$gruppe = $this->input->post('gruppe', true);
$result = $this->stundenplanlib->getEventsStgOrg($start_date, $end_date, $stg_kz, $sem, $verband, $gruppe);
$stgOrgEvents = $this->getDataOrTerminateWithError($result);
$result = $this->stundenplanlib->fetchFerienTageEvents($start_date, $end_date, $stg_kz);
$ferienEvents = $this->getDataOrTerminateWithError($result);
}
$this->terminateWithSuccess(array_merge(
$stgOrgEvents,
$ferienEvents
));
}
/**
* fetches LvPlan and Ferien events together for the lv
*
@@ -185,6 +137,7 @@ class LvPlan extends FHCAPI_Controller
// fetching ferien events
$ferienEvents = $this->fetchFerienEvents($start_date, $end_date);
$this->terminateWithSuccess(array_merge(
$lvplanEvents,
@@ -334,48 +287,6 @@ class LvPlan extends FHCAPI_Controller
return $this->terminateWithSuccess(current($result));
}
public function getStudiengaenge()
{
$this->load->model('organisation/Studiengang_model','StudiengangModel');
$this->StudiengangModel->addOrder('typ');
$this->StudiengangModel->addOrder('kurzbz');
$result = $this->StudiengangModel->loadWhere([
'aktiv' => true
]);
$data = $this->getDataOrTerminateWithError($result);
return $this->terminateWithSuccess($data);
}
public function getLehrverband($studiengang_kz, $semester=null, $verband=null)
{
$this->load->model('organisation/Lehrverband_model','LehrverbandModel');
$where = [
'aktiv' => true,
'studiengang_kz' => $studiengang_kz,
];
if ($semester !== null && $semester !== 'null' && $semester !== 'undefined') {
$where['semester'] = $semester;
}
if ($verband !== null && $verband !== 'null' && $verband !== 'undefined') {
$where['verband'] = $verband;
}
$this->LehrverbandModel->addOrder('studiengang_kz');
$this->LehrverbandModel->addOrder('semester');
$this->LehrverbandModel->addOrder('verband');
$this->LehrverbandModel->addOrder('gruppe');
$result = $this->LehrverbandModel->loadWhere($where);
$data = $this->getDataOrTerminateWithError($result);
return $this->terminateWithSuccess($data);
}
/**
* fetch moodle events
*
@@ -427,7 +338,7 @@ class LvPlan extends FHCAPI_Controller
$currentStudiensemester = $this->StudiensemesterModel->getByDate($start_date);
$currentStudiensemester = $this->getDataOrTerminateWithError($currentStudiensemester);
if ($currentStudiensemester) {
$studentsemester_kurzbz = current($currentStudiensemester)->studiensemester_kurzbz;
@@ -436,7 +347,7 @@ class LvPlan extends FHCAPI_Controller
"studiensemester_kurzbz" => $studentsemester_kurzbz
]);
$studiengang = $this->getDataOrTerminateWithError($studiengang);
if ($studiengang)
$studiengang_kz = current($studiengang)->studiengang_kz;
else
@@ -446,7 +357,7 @@ class LvPlan extends FHCAPI_Controller
}
$ferienEvents = $this->stundenplanlib->fetchFerienTageEvents($start_date, $end_date, $studiengang_kz);
return $this->getDataOrTerminateWithError($ferienEvents);
}
}
@@ -1,121 +0,0 @@
<?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);
}
}
@@ -1,200 +0,0 @@
<?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')));
}
}
@@ -1,159 +0,0 @@
<?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();
}
}
@@ -1,137 +0,0 @@
<?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,7 +24,6 @@ class NotizPerson extends Notiz_Controller
//Load Models
$this->load->model('person/Benutzer_model', 'BenutzerModel');
$this->load->model('crm/Student_model', 'StudentModel');
$this->load->model('crm/Prestudent_model', 'PrestudentModel');
//Permission checks for allowed Oes
if ($this->router->method == 'addNewNotiz')
@@ -39,7 +38,7 @@ class NotizPerson extends Notiz_Controller
{
return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Person ID']), self::ERROR_TYPE_GENERAL);
}
$this->_checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs);
$this->_checkIfBerechtigungForOneUidExists($person_id, $allowedStgs);
}
if ( $this->router->method == 'updateNotiz')
@@ -60,7 +59,7 @@ class NotizPerson extends Notiz_Controller
$person_id = current($data)->person_id;
$allowedStgs = $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: [];
$this->_checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs);
$this->_checkIfBerechtigungForOneUidExists($person_id, $allowedStgs);
}
if ($this->router->method == 'deleteNotiz' )
@@ -79,7 +78,7 @@ class NotizPerson extends Notiz_Controller
}
$allowedStgs = $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: [];
$this->_checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs);
$this->_checkIfBerechtigungForOneUidExists($person_id, $allowedStgs);
}
}
@@ -100,20 +99,44 @@ class NotizPerson extends Notiz_Controller
}
//stv: if person has permission of one studiengang of person -> permission to add/update/delete Note
private function _checkIfBerechtigungForOnePrestudentExists($person_id, $allowedStgs)
private function _checkIfBerechtigungForOneUidExists($person_id, $allowedStgs)
{
$result = $this->PrestudentModel->loadWhere(['person_id' => $person_id]);
//get all studentUids of person_id
$result = $this->BenutzerModel->loadWhere(['person_id' => $person_id]);
$data = $this->getDataOrTerminateWithError($result);
$checkarray = [];
foreach ($data as $item)
{
if(in_array($item->studiengang_kz, $allowedStgs))
{
return true;
}
}
//check if isStudent
$result = $this->StudentModel->isStudent($item->uid);
$this->terminateWithError($this->p->t('ui', 'error_keineBerechtigungStg'), self::ERROR_TYPE_GENERAL);
$isStudent = $this->getDataOrTerminateWithError($result);
if($isStudent)
{
$checkarray[] = $this->_checkAllowedStgsFromUid($item->uid, $allowedStgs);
}
}
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->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;
}
}
}
@@ -60,8 +60,8 @@ class LvTermine extends FHCAPI_Controller
$semester_range,
$benutzer_gruppen,
$student_lehrverband,
true,
$dbStundenplanTable
$dbStundenplanTable,
true
);
if(!$stundenplan_query)
@@ -626,7 +626,7 @@ class Students extends FHCAPI_Controller
$this->addFilter($studiensemester_kurzbz);
$result = $this->PrestudentModel->loadWhere($where);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
@@ -851,44 +851,40 @@ class Students extends FHCAPI_Controller
$stdsemEsc = $studiensemester_kurzbz ? $this->PrestudentModel->escape($studiensemester_kurzbz) : 'NULL';
$this->load->config('stv');
$tags = $this->config->item('stv_prestudent_tags');
if(defined('STV_TAGS_ENABLED') && STV_TAGS_ENABLED)
{
$tags = $this->config->item('stv_prestudent_tags');
$whereTags = '';
if (is_array($tags) && !isEmptyArray($tags)) {
$tags = array_keys($tags);
$whereTags = '';
if (is_array($tags) && !isEmptyArray($tags)) {
$tags = array_keys($tags);
foreach ($tags as $key => $tag) {
$tags[$key] = $this->db->escape($tag);
}
$whereTags = " AND nt.typ_kurzbz IN (" . implode(",", $tags) . ")";
foreach ($tags as $key => $tag) {
$tags[$key] = $this->db->escape($tag);
}
$subQueryTag = "
(
SELECT
tag.prestudent_id,
COALESCE(json_agg(tag ORDER BY tag.done), '[]'::json) AS tags
FROM (
SELECT DISTINCT ON (n.notiz_id)
n.notiz_id AS id,
nt.typ_kurzbz,
array_to_json(nt.bezeichnung_mehrsprachig)->>0 AS beschreibung,
n.text AS notiz,
nt.style,
n.erledigt AS done,
nz.prestudent_id
FROM public.tbl_notizzuordnung AS nz
JOIN public.tbl_notiz AS n ON nz.notiz_id = n.notiz_id
JOIN public.tbl_notiz_typ AS nt ON n.typ = nt.typ_kurzbz "
. $whereTags .
"
) AS tag
GROUP BY tag.prestudent_id
) AS tag_data_agg
";
$whereTags = " AND nt.typ_kurzbz IN (" . implode(",", $tags) . ")";
}
$subQueryTag = "
(
SELECT
tag.prestudent_id,
COALESCE(json_agg(tag ORDER BY tag.done), '[]'::json) AS tags
FROM (
SELECT DISTINCT ON (n.notiz_id)
n.notiz_id AS id,
nt.typ_kurzbz,
array_to_json(nt.bezeichnung_mehrsprachig)->>0 AS beschreibung,
n.text AS notiz,
nt.style,
n.erledigt AS done,
nz.prestudent_id
FROM public.tbl_notizzuordnung AS nz
JOIN public.tbl_notiz AS n ON nz.notiz_id = n.notiz_id
JOIN public.tbl_notiz_typ AS nt ON n.typ = nt.typ_kurzbz "
. $whereTags .
"
) AS tag
GROUP BY tag.prestudent_id
) AS tag_data_agg
";
$this->PrestudentModel->addJoin('public.tbl_studiengang stg', 'studiengang_kz', 'LEFT');
$this->PrestudentModel->addJoin('public.tbl_person p', 'person_id');
@@ -911,17 +907,11 @@ class Students extends FHCAPI_Controller
AND ps.studiensemester_kurzbz=public.get_stdsem_prestudent(tbl_prestudent.prestudent_id, ' . $stdsemEsc . ')
AND ps.ausbildungssemester=public.get_absem_prestudent(tbl_prestudent.prestudent_id, ' . $stdsemEsc . ')', 'LEFT');
if(defined('STV_TAGS_ENABLED') && STV_TAGS_ENABLED)
{
$this->PrestudentModel->addJoin($subQueryTag, 'tag_data_agg.prestudent_id = tbl_prestudent.prestudent_id', 'LEFT');
}
$this->PrestudentModel->addJoin($subQueryTag, 'tag_data_agg.prestudent_id = tbl_prestudent.prestudent_id', 'LEFT');
$this->PrestudentModel->addSelect("b.uid");
if(defined('STV_TAGS_ENABLED') && STV_TAGS_ENABLED)
{
$this->PrestudentModel->addSelect('tag_data_agg.tags');
}
$this->PrestudentModel->addSelect('tag_data_agg.tags');
$this->PrestudentModel->addSelect('titelpre');
$this->PrestudentModel->addSelect('nachname');
$this->PrestudentModel->addSelect('vorname');
@@ -1,52 +0,0 @@
<?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
@@ -0,0 +1,76 @@
<?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');
}
}
@@ -0,0 +1,216 @@
<?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);
}
}
@@ -0,0 +1,86 @@
<?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) ?: []);
}
}
@@ -0,0 +1,58 @@
<?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');
}
}
@@ -0,0 +1,134 @@
<?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,10 +495,6 @@ class AbgabetoolJob extends JOB_Controller
// get all new or changed termine in interval
$result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSince($interval, $relevantTypes);
$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
$projektarbeiten = [];
@@ -561,8 +557,6 @@ class AbgabetoolJob extends JOB_Controller
$anredeFillString = $data->anrede == "Herr" ? "r" : "";
$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) {
$projektarbeit_id = $tupel[0];
$betreuerRow = $tupel[1];
@@ -581,8 +575,6 @@ class AbgabetoolJob extends JOB_Controller
continue;
}
$relevantCounter++;
// format the Student Name
$s = $relevantAbgaben[0];
$nameParts = [];
@@ -641,11 +633,6 @@ class AbgabetoolJob extends JOB_Controller
// done with building the change list, now send it
$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');
$url = CIS_ROOT.$path;
+1 -16
View File
@@ -50,7 +50,6 @@ class PermissionLib
const LOGINAS_PERSONIDS_BLACKLIST = 'permission_loginas_personids_blacklist';
private $_ci; // CI instance
private $access_rights; // current users access rights
private static $bb; // benutzerberechtigung
/**
@@ -62,8 +61,6 @@ class PermissionLib
// Loads CI instance
$this->_ci =& get_instance();
$this->access_rights = null;
$this->_ci->config->load('permission'); // Loads permission configuration
// If it's NOT called from command line
@@ -72,10 +69,8 @@ class PermissionLib
// API Caller rights initialization
$authObj = $this->_ci->authlib->getAuthObj();
self::$bb = new benutzerberechtigung();
if ($authObj) {
if ($authObj)
self::$bb->getBerechtigungen($authObj->{AuthLib::AO_USERNAME});
$this->access_rights = self::$bb->berechtigungen;
}
}
}
@@ -345,16 +340,6 @@ class PermissionLib
}
}
/**
* Returns the access rights for the current user
*
* @return array|null
*/
public function getAccessRights()
{
return $this->access_rights;
}
//------------------------------------------------------------------------------------------------------------------
// Private methods
-18
View File
@@ -445,24 +445,6 @@ class StundenplanLib
return success($ferienEventsFlattened);
}
public function getEventsStgOrg( $start, $end, $stg_kz, $sem, $verband, $gruppe)
{
$this->_ci =& get_instance();
$this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel');
$stundenplan_data = $this->_ci->StundenplanModel->getStundenplanStudiengang($start, $end, $stg_kz, $sem, $verband, $gruppe);
if (isError($stundenplan_data))
return $stundenplan_data;
$stundenplan_data = getData($stundenplan_data) ?? [];
$function_error = $this->expandObjectInformation($stundenplan_data);
if ($function_error)
return $function_error;
return success($stundenplan_data);
}
// start of the private functions ########################################################################################################
// function used to sort an array of studiensemester strings
@@ -49,7 +49,7 @@ class DashboardLib
public function getMergedConfig($dashboard_id, $uid)
{
$defaultconfig = $this->getDefaultConfig($dashboard_id);
$defaultconfig = $this->getDefaultConfig($dashboard_id, $uid);
$userconfig = $this->getUserConfig($dashboard_id, $uid);
$mergedconfig = array_replace_recursive($defaultconfig, $userconfig);
@@ -57,31 +57,14 @@ class DashboardLib
return $mergedconfig;
}
public function getDefaultConfig($dashboard_id)
public function getDefaultConfig($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
]);
$res_presets = $this->_ci->DashboardPresetModel->getPresets($dashboard_id, $uid);
$defaultconfig = array();
if (hasData($result))
if (hasData($res_presets))
{
$presets = getData($result);
$presets = getData($res_presets);
foreach ($presets as $presetobj)
{
$preset = json_decode($presetobj->preset, true);
@@ -154,10 +137,8 @@ class DashboardLib
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
$funktion_kurzbz = ($section === self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL) ? null : $section;
$result = $this->_ci->DashboardPresetModel->loadWhere([
'dashboard_id' => $dashboard->dashboard_id,
'funktion_kurzbz' => $funktion_kurzbz
]);
$result = $this->_ci->DashboardPresetModel
->getPresetByDashboardAndFunktion($dashboard->dashboard_id, $funktion_kurzbz);
if (hasData($result))
{
@@ -214,11 +195,11 @@ class DashboardLib
{
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,4 +11,57 @@ class Dashboard_Preset_model extends DB_Model
$this->dbTable = 'dashboard.tbl_dashboard_preset';
$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));
}
}
@@ -388,84 +388,6 @@ class Stundenplan_model extends DB_Model
ORDER BY datum, beginn", [$start_date, $end_date, $ma_uid]);
}
/**
* queries Stundenplan and filters by studiengang, semester, verband gruppe
*
* @return void
*/
public function getStundenplanStudiengang($start_date, $end_date, $stg_kz, $sem, $verband, $gruppe) {
$qry_params = [$start_date, $end_date, $stg_kz];
$qry = "
SELECT
'lehreinheit' as type, beginn, ende, datum,
CONCAT(lehrfach,'-',lehrform) as topic,
array_agg(DISTINCT lektor) as lektor,
array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe,
string_agg(DISTINCT ort_kurzbz, '/') as ort_kurzbz,
array_agg(DISTINCT lehreinheit_id) as lehreinheit_id,
titel, lehrfach, lehrform, lehrfach_bez, organisationseinheit, farbe, lehrveranstaltung_id
FROM
(
SELECT unr,datum,beginn, ende,
CASE
WHEN sp.mitarbeiter_kurzbz IS NOT NULL THEN sp.mitarbeiter_kurzbz
ELSE sp.lektor
END as lektor,
CASE
WHEN gruppe_kurzbz IS NOT NULL THEN gruppe_kurzbz
ELSE CONCAT(UPPER(sp.stg_typ),UPPER(sp.stg_kurzbz),'-',COALESCE(CAST(sp.semester AS varchar),'/'),COALESCE(CAST(sp.verband AS varchar),'/'))
END as gruppen_kuerzel,
(SELECT bezeichnung
FROM public.tbl_organisationseinheit
WHERE oe_kurzbz IN(
SELECT oe_kurzbz
FROM lehre.tbl_lehrveranstaltung
WHERE lehrveranstaltung_id = sp.lehrveranstaltung_id
)) as organisationseinheit,
sp.ort_kurzbz, sp.studiengang_kz, sp.titel,sp.lehreinheit_id,sp.lehrfach_id,sp.anmerkung,fix,lehrveranstaltung_id,stg_kurzbzlang,stg_bezeichnung,stg_typ,fachbereich_kurzbz,lehrfach,lehrfach_bez,farbe,lehrform,anmerkung_lehreinheit,gruppe, verband, semester,stg_kurzbz
FROM (
SELECT sp.*
FROM lehre.vw_stundenplan sp
WHERE
sp.datum >= ?
AND sp.datum <= ?
) sp
JOIN lehre.tbl_stunde ON lehre.tbl_stunde.stunde = sp.stunde
WHERE studiengang_kz = ? ";
if($sem != NULL)
{
$qry_params[] = $sem;
$qry .= " AND (semester = ? OR semester IS NULL)";
}
if($verband != NULL)
{
$qry_params[] = $verband;
$qry .= " AND (verband = ? OR verband IS NULL OR verband = '0' OR verband = '')";
}
if($gruppe != NULL)
{
$qry_params[] = $gruppe;
$qry .= " AND (gruppe = ? OR gruppe IS NULL OR gruppe = '0' OR gruppe = '') ";
}
$qry.= " AND (
gruppe_kurzbz is null OR EXISTS(
SELECT 1
FROM
public.tbl_gruppe WHERE gruppe_kurzbz = sp.gruppe_kurzbz AND direktinskription = false
)
)";
$qry.= " ) as subquery
GROUP BY unr, datum, beginn, ende, titel, lehrform, lehrfach, lehrfach_bez, organisationseinheit, farbe, lehrveranstaltung_id
ORDER BY datum, beginn; ";
return $this->execReadOnlyQuery($qry, $qry_params);
}
/**
* NO STANDALONE FUNCTION - Generates a SQL query string to fetch 'stundenplan' events for a specific student within the current semester.
@@ -475,8 +397,8 @@ class Stundenplan_model extends DB_Model
*
* @return mixed
*/
public function getStundenplanQuery($start_date, $end_date, $semester, $gruppen, $studentlehrverbaende, $isLvList=false, $db_stpl_table='stundenplan'){
public function getStundenplanQuery($start_date, $end_date, $semester, $gruppen, $studentlehrverbaende, $db_stpl_table='stundenplan', $showLvsStundenplan=false)
{
// helper function to check if either $gruppen or $studentlehrverbaende are empty for each semester
$emptyCheck = function($toBeCheckedArray) use ($semester){
$result = true;
@@ -532,12 +454,6 @@ class Stundenplan_model extends DB_Model
$query = substr($query, 0, -2);
}
//Condition for showLVList FHC4
if(!$isLvList)
$stringGroupLv = "AND gruppe_kurzbz is null";
else
$stringGroupLv ="";
foreach($semester as $sem=>$semester_date_range)
{
foreach($semester_date_range as $sem_date => $sem_date_range)
@@ -555,7 +471,22 @@ class Stundenplan_model extends DB_Model
$query .= "OR (sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND (sp.verband is null OR sp.verband='') AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)
." AND ".$this->escape($sem_date_range->ende).")) AND gruppe_kurzbz is null)";
$query .="OR";
// Eintraege vom Stundenplan
if($showLvsStundenplan)
{
$query .= "
OR EXISTS (
SELECT 1
FROM lehre.tbl_stundenplan tsp
WHERE tsp.lehreinheit_id = sp.lehreinheit_id
AND tsp.studiengang_kz = " . $this->escape($lehrverband->studiengang_kz) . "
AND (tsp.semester = " . $this->escape($lehrverband->semester) . " OR tsp.semester IS NULL)
AND (tsp.verband = " . $this->escape($lehrverband->verband) . " OR tsp.verband IS NULL OR tsp.verband = '0' OR tsp.verband = '')
AND (tsp.gruppe = " . $this->escape($lehrverband->gruppe) . " OR tsp.gruppe IS NULL OR tsp.gruppe = '0' OR tsp.gruppe = '')
AND tsp.gruppe_kurzbz IS NULL)";
}
$query .= "OR";
}
}
}
@@ -8,15 +8,9 @@ $this->load->view(
'axios027' => true,
'restclient' => true,
'vue3' => true,
'primevue3' => true,
'vuedatepicker11' => true,
'customJSs' => [
'vendor/moment/luxonjs/luxon.min.js'
],
'customJSModules' => ['public/js/apps/Dashboard/Admin.js'],
'customJSModules' => ['public/js/apps/Dashboard.js'],
'customCSSs' => [
'public/css/components/dashboard.css',
'public/css/components/primevue.css',
'public/css/components/dashboard.css'
],
'navigationcomponent' => true
)
@@ -31,7 +25,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">
<h1 class="h2">Dashboard</h1>
</div>
<dashboard-admin></dashboard-admin>
<core-dashboard dashboard="CIS" apiurl="<?= site_url('dashboard'); ?>"></core-dashboard>
</div>
</div>
@@ -8,12 +8,7 @@ $this->load->view(
'axios027' => true,
'restclient' => true,
'vue3' => true,
'vuedatepicker11' => true,
'primevue3' => true,
'customJSs' => [
'vendor/moment/luxonjs/luxon.min.js'
],
'customJSModules' => ['public/js/apps/Dashboard/Preview.js'],
'customJSModules' => ['public/js/apps/DashboardAdmin.js'],
'customCSSs' => [
'public/css/components/dashboard.css'
],
@@ -28,9 +23,9 @@ $this->load->view(
<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">
<h1 class="h2">Dashboard <?= $dashboard_kurzbz ?></h1>
<h1 class="h2">Dashboard</h1>
</div>
<core-dashboard dashboard="<?= $dashboard_kurzbz ?>"></core-dashboard>
<dashboard-admin dashboard="CIS" apiurl="<?= site_url('dashboard'); ?>"></dashboard-admin>
</div>
</div>
@@ -802,10 +802,6 @@ echo '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>';
class="sortDirectionIndicator"
sort="rdf:http://www.technikum-wien.at/prestudentrolle/rdf#fgm" />
<splitter class="tree-splitter"/>
<treecol id="student-prestudent-tree-rolle-faktiv" label="F-Aktiv" flex="1" hidden="true" persist="hidden, width, ordinal"
class="sortDirectionIndicator"
sort="rdf:http://www.technikum-wien.at/prestudentrolle/rdf#faktiv" />
<splitter class="tree-splitter"/>
</treecols>
<template>
@@ -832,7 +828,6 @@ echo '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>';
<treecell properties="Aktiv_rdf:http://www.technikum-wien.at/prestudentrolle/rdf#aktiv rdf:http://www.technikum-wien.at/prestudentrolle/rdf#stichtagsaktiv" label="rdf:http://www.technikum-wien.at/prestudentrolle/rdf#updateamum"/>
<treecell properties="Aktiv_rdf:http://www.technikum-wien.at/prestudentrolle/rdf#aktiv rdf:http://www.technikum-wien.at/prestudentrolle/rdf#stichtagsaktiv" label="rdf:http://www.technikum-wien.at/prestudentrolle/rdf#updatevon"/>
<treecell properties="Aktiv_rdf:http://www.technikum-wien.at/prestudentrolle/rdf#aktiv rdf:http://www.technikum-wien.at/prestudentrolle/rdf#stichtagsaktiv" label="rdf:http://www.technikum-wien.at/prestudentrolle/rdf#fgm"/>
<treecell properties="Aktiv_rdf:http://www.technikum-wien.at/prestudentrolle/rdf#aktiv rdf:http://www.technikum-wien.at/prestudentrolle/rdf#stichtagsaktiv" label="rdf:http://www.technikum-wien.at/prestudentrolle/rdf#faktiv"/>
</treerow>
</treeitem>
</treechildren>
-2
View File
@@ -3550,11 +3550,9 @@ function StudentZeugnisDokumentArchivieren()
case 'microcredentialzertifikat_1':
case 'microcredentialzertifikat_2':
case 'microcredentialzertifikat_3':
case 'microcredentialzertifikat_4':
case 'microcredential_1':
case 'microcredential_2':
case 'microcredential_3':
case 'microcredential_4':
xml = 'microcredential.xml.php';
break;
+7 -162
View File
@@ -552,40 +552,9 @@ class lehreinheitmitarbeiter extends basis_db
$beginn = new DateTime($beginn);
$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 = '
WITH semester_sws_tbl AS (
SELECT DISTINCT lehreinheit_id, studiensemester_kurzbz, lema.semesterstunden,
stg.studiengang_kz, stg.melde_studiengang_kz, stg.lgartcode
SELECT DISTINCT lehreinheit_id, studiensemester_kurzbz, lema.semesterstunden, stg.studiengang_kz
FROM lehre.tbl_lehreinheitmitarbeiter lema
JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
JOIN lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id)
@@ -595,103 +564,38 @@ class lehreinheitmitarbeiter extends basis_db
JOIN public.tbl_studiengang stg ON stg.studiengang_kz = sto.studiengang_kz
JOIN public.tbl_studiensemester ss USING (studiensemester_kurzbz)
WHERE mitarbeiter_uid = '. $this->db_add_param($uid). '
AND ss.studiensemester_kurzbz IN ('.$this->implode4SQL($studiensemester_kurzbz_arr).')
AND (
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
AND lema.bismelden
-- keine lehreinheiten ohne semesterstunden
AND lema.semesterstunden != 0
AND lema.semesterstunden != 0
)
SELECT
studiengang_kz,
studiensemester_kurzbz,
melde_studiengang_kz,
lgartcode,
sum(semesterstunden) AS summe,
round(sum(semesterstunden) / 15, 2) AS sws
FROM
semester_sws_tbl
GROUP BY
studiengang_kz,
studiensemester_kurzbz,
melde_studiengang_kz,
lgartcode
studiensemester_kurzbz
ORDER BY
studiengang_kz;
';
if ($this->db_query($qry))
{
$additionalLehrgaenge = [];
while($row = $this->db_fetch_object())
{
$obj = new StdClass();
$obj->studiengang_kz = $row->studiengang_kz;
$obj->studiensemester_kurzbz = $row->studiensemester_kurzbz;
$obj->melde_studiengang_kz = $row->melde_studiengang_kz;
$obj->lgartcode = $row->lgartcode;
$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;
}
return true;
@@ -751,63 +655,4 @@ class lehreinheitmitarbeiter extends basis_db
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,8 +270,6 @@ class LehreListHelper
} 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
$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').')';
}
-1
View File
@@ -706,7 +706,6 @@ class prestudent extends person
$rolle->bestaetigtam = $row->bestaetigtam;
$rolle->bestaetigtvon = $row->bestaetigtvon;
$rolle->fgm = $row->fgm;
$rolle->faktiv = $this->db_parse_bool($row->faktiv);
$rolle->anmerkung_status = $row->anmerkung;
$rolle->bewerbung_abgeschicktamum = $row->bewerbung_abgeschicktamum;
$rolle->rt_stufe = $row->rt_stufe;
-1
View File
@@ -2,7 +2,6 @@
@import './SvgIcons.css';
@import './components/searchbar/searchbar.css';
@import './components/verticalsplit.css';
@import './components/horizontalsplit.css';
@import './components/FilterComponent.css';
@import './components/Tabs.css';
@import './components/Notiz.css';
-3
View File
@@ -193,6 +193,3 @@
word-break: break-word;
}
.news-list-item p {
word-break: break-word;
}
-73
View File
@@ -1,73 +0,0 @@
:root {
--fhc-horizontalsplit-hsplitter-bg-color: var(--fhc-background, #eee);
--fhc-horizontalsplit-hsplitter-border-color: var(--fhc-border, #eee);
--fhc-horizontalsplit-hsplitter-splitactions-color: var(--fhc-dark, #000);
}
.horizontalsplit-container {
display: flex;
flex-direction: row;
overflow: hidden;
max-height: 100%;
padding: 0px;
}
.horizontalsplitted {
overflow: auto;
flex-shrink: 0;
}
.horizontalsplitter {
flex-shrink: 0;
width: 8px;
cursor: col-resize;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
}
.horizontalsplitter.left {
border-left: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color);
}
.horizontalsplitter.right {
border-right: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color);
}
.splitactions.horizontal {
background-color: var(--fhc-horizontalsplit-hsplitter-bg-color);
color: var(--fhc-horizontalsplit-hsplitter-splitactions-color);
display: flex;
flex-direction: column;
padding: 5px 0 5px 0;
}
.splitactions.horizontal.left {
border-radius: 0 40% 40% 0;
}
.splitactions.horizontal.right {
border-radius: 40% 0 0 40%;
}
.splitactions.horizontal .splitaction {
width: auto;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
.splitactions.horizontal .splitaction.resize {
cursor: col-resize;
}
#content {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
#content > div:first-child {
margin-top: 30px;
}
-46
View File
@@ -1,46 +0,0 @@
/**
* 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
@@ -1,47 +0,0 @@
/**
* 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
@@ -1,45 +0,0 @@
/**
* 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
@@ -1,46 +0,0 @@
/**
* 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
}
};
}
};
+1 -27
View File
@@ -92,31 +92,5 @@ export default {
method: 'get',
url: '/api/frontend/v1/LvPlan/getLv/' + lehrveranstaltung_id
};
},
eventsStgOrg(start_date, end_date, stg_kz, sem, verband, gruppe) {
return {
method: 'post',
url: '/api/frontend/v1/lvPlan/eventsStgOrg',
params: { start_date, end_date, stg_kz, sem, verband, gruppe }
};
},
getStudiengaenge(){
return {
method: 'get',
url: '/api/frontend/v1/lvPlan/getStudiengaenge'
}
},
getLehrverband(stg_kz, sem){
return {
method: 'get',
url: `/api/frontend/v1/lvPlan/getLehrverband/${stg_kz}/${sem}`
}
},
getGruppe(stg_kz, sem, verband){
return {
method: 'get',
url: `/api/frontend/v1/lvPlan/getLehrverband/${stg_kz}/${sem}/${verband}`
}
},
}
};
+2 -1
View File
@@ -23,7 +23,8 @@ export default {
+ params.start_date + '/'
+ params.end_date + '/'
+ params.group_consecutiveHours + '/'
+ params.dbStundenplanTable
+ params.dbStundenplanTable + '/'
+ params.showLvsStundenplan
};
},
getStudiensemester(){
+9 -60
View File
@@ -1,67 +1,16 @@
import { CoreNavigationCmpt } from '../../components/navigation/Navigation.js';
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import DashboardAdmin from '../../components/Dashboard/Admin.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
import ApiRenderers from '../../api/factory/renderers.js';
const app = Vue.createApp({
name: 'DashboardAdminApp',
data: () => ({
appSideMenuEntries: {},
renderers: null
}),
components: {
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);
}
name: 'AdminApp',
data: () => ({
appSideMenuEntries: {}
}),
components: {
CoreNavigationCmpt,
DashboardAdmin
}
});
app.use(PluginsPhrasen);
app.directive('tooltip', primevue.tooltip);
app.mount('#main');
-22
View File
@@ -17,8 +17,6 @@ import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMit
import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js";
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
import Studium from "../../components/Cis/Studium/Studium.js";
import StgOrgLvPlan from "../../components/Cis/LvPlan/StgOrg.js";
import OverviewLvPlan from "../../components/Cis/LvPlan/OverviewLvPlan.js";
import ApiRenderers from '../../api/factory/renderers.js';
import ApiRouteInfo from '../../api/factory/routeinfo.js';
@@ -199,26 +197,6 @@ const router = VueRouter.createRouter({
};
}
},
{
path: `/Cis/StgOrgLvPlan/:mode?/:focus_date?/:stgkz?/:sem?/:verband?/:gruppe?`,
name: 'StgOrgLvPlan',
component: StgOrgLvPlan,
props(route) {
return {
propsViewData: route.params
};
}
},
{
path: `/Cis/OverviewLvPlan`,
name: 'OverviewLvPlan',
component: OverviewLvPlan,
props(route) {
return {
propsViewData: route.params
};
}
},
{
path: `/Cis4`,
name: 'Cis4',
-17
View File
@@ -1,17 +0,0 @@
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 v-slot:footer>
<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">{{this.$p.t('ui', 'cancel')}}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</template>
</bs-modal>`
}
+2 -6
View File
@@ -88,15 +88,12 @@ export default {
updateRange(rangeInterval) {
this.rangeInterval = rangeInterval;
this.$emit('update:range', rangeInterval);
},
resetEventLoader() {
this.reset();
}
},
setup(props, context) {
const rangeInterval = Vue.ref(null);
const { events, lv, reset } = useEventLoader(rangeInterval, props.getPromiseFunc);
const { events, lv } = useEventLoader(rangeInterval, props.getPromiseFunc);
Vue.watch(lv, newValue => {
context.emit('update:lv', newValue);
@@ -105,8 +102,7 @@ export default {
return {
rangeInterval,
events,
lv,
reset
lv
};
},
created() {
@@ -180,7 +180,7 @@ export const AbgabetoolAssistenz = {
// frozen: true,
// width: 40
// },
{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/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/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/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},
],
persistence: false,
persistenceID: "abgabetool_2026_03_16"
persistenceID: "abgabetool_2026_02_26"
},
abgabeTableEventHandlers: [
{
@@ -645,7 +645,7 @@ export const AbgabetoolAssistenz = {
actionButtons.className = "d-flex gap-3"; // you can keep Bootstrap gap if loaded
actionButtons.style.display = "flex";
actionButtons.style.alignItems = "stretch"; // buttons stretch to full height
actionButtons.style.justifyContent = "start";
actionButtons.style.justifyContent = "center";
actionButtons.style.height = "100%"; // full grid cell height
const val = cell.getValue();
@@ -675,20 +675,8 @@ export const AbgabetoolAssistenz = {
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;
},
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) {
// checks if cells row is selected and unselects -> imitates columns which dont trigger row selection
@@ -792,8 +780,6 @@ export const AbgabetoolAssistenz = {
// TODO: mehrsprachig englisch
projekt.note_bez = opt.bezeichnung
}
const latestTerminWithUpload = this.findLatestTerminWithUpload(projekt)
return {
...projekt,
@@ -801,7 +787,6 @@ export const AbgabetoolAssistenz = {
details: {
student_uid: projekt.student_uid,
projektarbeit_id: projekt.projektarbeit_id,
latestTerminWithUpload: latestTerminWithUpload ?? null
},
pkz: this.buildPKZ(projekt),
beurteilung: projekt.beurteilungLink ?? null,
@@ -815,15 +800,6 @@ 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) {
let str = '';
@@ -1437,11 +1413,8 @@ export const AbgabetoolAssistenz = {
<div id="abgabetable" style="max-height:40vw;">
<div class="row">
<div class="col-auto me-auto">
<h2 tabindex="1">{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
</div>
<div class="col-auto">
<label class="col-form-label">{{$capitalize($p.t('lehre/studiengang'))}}:</label>
<h2 tabindex="1">{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
</div>
<div class="col-3">
<Dropdown
@@ -1457,9 +1430,6 @@ export const AbgabetoolAssistenz = {
</template>
</Dropdown>
</div>
<div class="col-auto">
<label class="col-form-label">{{$capitalize($p.t('lehre/note'))}}:</label>
</div>
<div class="col-3">
<Dropdown
:placeholder="$p.t('lehre/note')"
@@ -667,10 +667,8 @@ export const AbgabetoolMitarbeiter = {
setDetailComponent(details){
this.loading=true
const projektarbeiten = this.projektarbeiten?.retval ?? this.projektarbeiten
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
const pa = projektarbeiten.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
let paIsBenotet = false
if(pa.note !== undefined && pa.note !== null) {
// check if the note is not defined as a non final projektarbeit note
@@ -1,194 +0,0 @@
import FormForm from '../../Form/Form.js';
import FormInput from '../../Form/Input.js';
import ApiLvPlan from '../../../api/factory/lvPlan.js';
export default {
name: "OverviewLvPlan",
components: {
FormForm,
FormInput,
},
props: {
viewData: Object,
propsViewData: Object
},
data() {
return {
formData: {
stgkz: null,
sem: null,
verband: null,
gruppe: null,
},
listStg: [],
listSem: [1,2,3,4,5,6,7,8,9,10],
listVerband: [],
listGroup: [],
};
},
methods: {
loadLvPlan() {
if (!this.formData.stgkz) {
this.$fhcAlert.alertError(this.$p.t('LvPlan', 'chooseStg'));
return;
}
if (!this.formData.sem && (this.formData.verband || this.formData.gruppe)) {
this.$fhcAlert.alertError(this.$p.t('LvPlan', 'error_SemMissing'));
return;
}
if (!this.formData.verband && this.formData.gruppe) {
this.$fhcAlert.alertError(this.$p.t('LvPlan', 'error_VerbandMissing'));
return;
}
const params = {
mode: this.currentMode,
focus_date: this.currentDay,
stgkz: this.formData.stgkz,
sem: this.formData.sem,
verband: this.formData.verband,
gruppe: this.formData.gruppe,
};
//ensure logic: no value after a null value in route
if (params.sem == null) {
params.verband = null;
params.gruppe = null;
}
if (params.verband == null) {
params.gruppe = null;
}
//delete all null values to avoid null in router
Object.keys(params).forEach(
key => params[key] == null && delete params[key]
);
this.$router.push({
name: "StgOrgLvPlan",
params,
});
},
loadListSem(){
this.listSem = [...Array(this.maxSemester).keys()].map(i => i + 1);
},
loadListVerband(){
this.$api
.call(ApiLvPlan.getLehrverband(this.formData.stgkz, this.formData.sem, this.formData.verband))
.then(result => {
const data = result.data;
const mappedData = data.map(item => item.verband);
this.listVerband = [...new Set(mappedData.filter(v =>
v !== null &&
v !== undefined &&
String(v).trim() !== ""
))]
.sort();
})
.catch(this.$fhcAlert.handleSystemError);
},
loadListGroup(){
this.$api
.call(ApiLvPlan.getGruppe(this.formData.stgkz, this.formData.sem, this.formData.verband))
.then(result => {
const data = result.data;
const mappedData = data.map(item => item.gruppe);
this.listGroup = [...new Set(mappedData.filter(v =>
v !== null &&
v !== undefined &&
String(v).trim() !== ""))]
.sort();
})
.catch(this.$fhcAlert.handleSystemError);
}
},
computed: {
maxSemester(){
const currentStg = this.listStg.find(
item => item.studiengang_kz === this.formData.stgkz
);
return currentStg.max_semester;
},
currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate();
return this.propsViewData?.focus_date;
},
},
created(){
this.$api
.call(ApiLvPlan.getStudiengaenge())
.then(result => {
this. listStg = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
},
template: `
<div cis-lvplan-stg-org-ues d-flex flex-column h-100>
<div class="mt-3">
<form-form class="row row-cols-1 row-cols-md-2 row-cols-lg-4 row-cols-xl-5 g-3 mb-3">
<form-input
type="select"
v-model="formData.stgkz"
@change="loadListSem(formData.stgkz)"
>
<option :value="null" selected>{{ $p.t('LvPlan/chooseStg') }}</option>
<option
v-for="stg in listStg"
:key="stg.studiengang_kz"
:value="stg.studiengang_kz"
>
{{ stg.kurzbzlang }} ({{ stg.bezeichnung }})
</option>
</form-input>
<form-input
type="select"
v-model="formData.sem"
@change="loadListVerband()"
@click="loadListSem(formData.stgkz)"
>
<option :value="null" selected>Semester</option>
<option
v-for="sem in listSem"
:key="sem"
:value="sem"
>
{{ sem }}
</option>
</form-input>
<form-input
type="select"
v-model="formData.verband"
@change="loadListGroup()"
>
<option :value="null" selected>{{ $p.t('lehre/verband') }} </option>
<option
v-for="verband in listVerband"
:key="verband"
:value="verband"
>
{{ verband }}
</option>
</form-input>
<form-input
type="select"
v-model="formData.gruppe"
>
<option :value="null" selected>{{ $p.t('gruppenmanagement/gruppe') }}</option>
<option
v-for="group in listGroup"
:key="group"
:value="group"
>
{{ group }}
</option>
</form-input>
<button type="button" class="btn btn-secondary" @click="loadLvPlan">{{ $p.t('LvPlan/loadLvPlan') }}</button>
</form-form>
</div>
</div>
`,
};
-336
View File
@@ -1,336 +0,0 @@
import FormForm from '../../Form/Form.js';
import FormInput from '../../Form/Input.js';
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../.././../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week';
export default {
name: 'LvPlanStgOrg',
components: {
FormForm,
FormInput,
FhcCalendar,
},
props: {
viewData: Object,
propsViewData: Object
},
data() {
return {
localProps: {},
studiensemester_kurzbz: null,
studiensemester_start: null,
studiensemester_ende: null,
uid: null,
isMitarbeiter: false,
isStudent: false,
currentStgBezeichnung: null,
formData: {
stgkz: null,
sem: null,
verband: null,
gruppe: null,
},
listStg: [],
listSem: [1,2,3,4,5,6,7,8,9,10],
listVerband: [],
listGroup: [],
rangeIntervalFirst: null
};
},
computed: {
maxSemester(){
const currentStg = this.listStg.find(
item => item.studiengang_kz === this.formData.stgkz
);
return currentStg.max_semester;
},
currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate();
return this.propsViewData?.focus_date;
},
currentMode() {
if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase()))
return DEFAULT_MODE_LVPLAN;
return this.propsViewData?.mode;
},
downloadLinks() {
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
return false;
let type = false;
type = this.isStudent ? 'student' : type;
type = this.isMitarbeiter ? 'lektor' : type;
if (false === type)
{
return;
}
const opts = { zone: this.viewData.timezone };
const start = luxon.DateTime
.fromISO(this.studiensemester_start, opts)
.toUnixInteger();
const ende = luxon.DateTime
.fromISO(this.studiensemester_ende, opts)
.toUnixInteger();
const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'cis/private/lvplan/stpl_kalender.php'
+ '?type=' + type
+ '&pers_uid=' + this.uid
+ '&begin=' + start
+ '&ende=' + ende;
return [
{ title: "excel", icon: 'fa-solid fa-file-excel', link: download_link + '&format=excel' },
{ title: "csv", icon: 'fa-solid fa-file-csv', link: download_link + '&format=csv' },
{ title: "ical1", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=1&target=ical' },
{ title: "ical2", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=2&target=ical' }
];
}
},
methods: {
loadLvPlan(){
if(!this.formData.stgkz){
this.$fhcAlert.alertError(this.$p.t('LvPlan', 'chooseStg'));
return;
}
if(!this.formData.sem && (this.formData.verband || this.formData.gruppe)){
this.$fhcAlert.alertError(this.$p.t('LvPlan', 'error_SemMissing'));
return;
}
if(!this.formData.verband && this.formData.gruppe){
this.$fhcAlert.alertError(this.$p.t('LvPlan', 'error_VerbandMissing'));
return;
}
const params = {
mode: this.currentMode,
focus_date: this.currentDay,
stgkz: this.formData.stgkz,
sem: this.formData.sem,
verband: this.formData.verband,
gruppe: this.formData.gruppe,
};
//ensure logic: no value after a null value in route
if(params.sem == null)
{
params.verband = null;
params.gruppe = null;
}
if(params.verband == null) {
params.gruppe = null;
}
//delete all null values to avoid null in router
Object.keys(params).forEach(
key => params[key] == null && delete params[key]
);
this.$router.push({
name: "StgOrgLvPlan",
params,
});
this.$refs['calendar'].resetEventLoader();
},
loadListSem(){
this.listSem = [...Array(this.maxSemester).keys()].map(i => i + 1);
},
loadListVerband(){
this.$api
.call(ApiLvPlan.getLehrverband(this.formData.stgkz, this.formData.semester, this.formData.verband))
.then(result => {
const data = result.data;
const mappedData = data.map(item => item.verband);
this.listVerband = [...new Set(mappedData.filter(v =>
v !== null &&
v !== undefined &&
String(v).trim() !== ""
))]
.sort();
})
.catch(this.$fhcAlert.handleSystemError);
},
loadListGroup(){
this.$api
.call(ApiLvPlan.getGruppe(this.formData.stgkz, this.formData.semester, this.formData.verband))
.then(result => {
const data = result.data;
const mappedData = data.map(item => item.gruppe);
this.listGroup = [...new Set(mappedData.filter(v =>
v !== null &&
v !== undefined &&
String(v).trim() !== ""))]
.sort();
})
.catch(this.$fhcAlert.handleSystemError);
},
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
},
handleChangeMode(newMode, day) {
const mode = newMode[0].toUpperCase() + newMode.slice(1);
const focus_date = day.toISODate();
this.$router.push({
name: "StgOrgLvPlan",
params: {
mode,
focus_date,
stgkz: this.formData.stgkz,
sem: this.formData.sem,
verband: this.formData.verband,
gruppe: this.formData.gruppe,
},
});
},
updateRange(rangeInterval) {
this.$api
.call(ApiLvPlan.studiensemesterDateInterval(
rangeInterval.end.startOf('week').toISODate()
))
.then(res => {
this.studiensemester_kurzbz = res.data.studiensemester_kurzbz;
this.studiensemester_start = res.data.start;
this.studiensemester_ende = res.data.ende;
});
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiLvPlan.eventsStgOrg(start, end, this.formData.stgkz, this.formData.sem, this.formData.verband, this.formData.gruppe))
];
},
},
created(){
this.$api
.call(ApiAuthinfo.getAuthInfo())
.then(res => {
this.uid = res.data.uid;
this.isMitarbeiter = res.data.isMitarbeiter;
this.isStudent = res.data.isStudent;
});
this.$api
.call(ApiLvPlan.getStudiengaenge())
.then(result => {
this. listStg = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
if(this.propsViewData) {
this.formData.stgkz = this.propsViewData.stgkz ? this.propsViewData.stgkz: null;
this.formData.sem = this.propsViewData.sem ? this.propsViewData.sem: null;
this.formData.verband = this.propsViewData.verband ? this.propsViewData.verband: null;
this.formData.gruppe = this.propsViewData.gruppe ? this.propsViewData.gruppe: null;
}
},
template: `
<div class="cis-lvplan-stg-org d-flex flex-column h-100">
<div class="mt-3">
<form-form class="row row-cols-1 row-cols-md-2 row-cols-lg-4 row-cols-xl-5 g-3 mb-3">
<form-input
type="select"
v-model="formData.stgkz"
@change="loadListSem(formData.stgkz)"
>
<option :value="null" selected>{{ $p.t('LvPlan/chooseStg') }}</option>
<option
v-for="stg in listStg"
:key="stg.studiengang_kz"
:value="stg.studiengang_kz"
>
{{ stg.kurzbzlang }} ({{ stg.bezeichnung }})
</option>
</form-input>
<form-input
type="select"
v-model="formData.sem"
@change="loadListVerband()"
@click="loadListSem(formData.stgkz)"
>
<option :value="null" selected>Semester</option>
<option
v-for="sem in listSem"
:key="sem"
:value="sem"
>
{{ sem }}
</option>
</form-input>
<form-input
type="select"
v-model="formData.verband"
@change="loadListGroup()"
>
<option :value="null" selected>{{ $p.t('lehre/verband') }} </option>
<option
v-for="verband in listVerband"
:key="verband"
:value="verband"
>
{{ verband }}
</option>
</form-input>
<form-input
type="select"
v-model="formData.gruppe"
>
<option :value="null" selected>{{ $p.t('gruppenmanagement/gruppe') }}</option>
<option
v-for="group in listGroup"
:key="group"
:value="group"
>
{{ group }}
</option>
</form-input>
<button type="button" class="btn btn-secondary" @click="loadLvPlan">{{ $p.t('LvPlan/loadLvPlan') }}</button>
</form-form>
</div>
<fhc-calendar
v-show="propsViewData && propsViewData.stgkz"
ref="calendar"
v-model:lv="formData"
:timezone="viewData.timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@update:range="updateRange"
class="responsive-calendar"
>
<div
v-if="downloadLinks"
class="d-flex gap-1 justify-items-start"
>
<div v-for="{ title, icon, link } in downloadLinks">
<a
:href="link"
:aria-label="title"
class="py-1 btn btn-outline-secondary"
>
<div class="d-flex flex-column">
<i aria-hidden="true" :class="icon"></i>
<span style="font-size:.5rem">{{ title }}</span>
</div>
</a>
</div>
</div>
</fhc-calendar>
</div>
`,
};
@@ -1,187 +0,0 @@
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../.././../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week';
export default {
name: 'LvPlanStgOrg',
inject: {
cisRoot: {
from: 'cisRoot'
},
},
components: {
FhcCalendar,
},
props: {
viewData: Object,
propsViewData: Object
},
data() {
return {
studiensemester_kurzbz: null,
studiensemester_start: null,
studiensemester_ende: null,
uid: null,
isMitarbeiter: false,
isStudent: false,
listStg: [],
currentStgBezeichnung: null
};
},
computed:{
currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate();
return this.propsViewData?.focus_date;
},
currentMode() {
if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase()))
return DEFAULT_MODE_LVPLAN;
return this.propsViewData?.mode;
},
downloadLinks() {
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
return false;
let type = false;
type = this.isStudent ? 'student' : type;
type = this.isMitarbeiter ? 'lektor' : type;
if (false === type)
{
return;
}
const opts = { zone: this.viewData.timezone };
const start = luxon.DateTime
.fromISO(this.studiensemester_start, opts)
.toUnixInteger();
const ende = luxon.DateTime
.fromISO(this.studiensemester_ende, opts)
.toUnixInteger();
const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'cis/private/lvplan/stpl_kalender.php'
+ '?type=' + type
+ '&pers_uid=' + this.uid
+ '&begin=' + start
+ '&ende=' + ende;
return [
{ title: "excel", icon: 'fa-solid fa-file-excel', link: download_link + '&format=excel' },
{ title: "csv", icon: 'fa-solid fa-file-csv', link: download_link + '&format=csv' },
{ title: "ical1", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=1&target=ical' },
{ title: "ical2", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=2&target=ical' }
];
}
},
methods: {
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
},
handleChangeMode(newMode, day) {
const mode = newMode[0].toUpperCase() + newMode.slice(1)
const focus_date = day.toISODate();
this.$router.push({
name: "StgOrgLvPlan",
params: {
mode,
focus_date,
stgkz: this.propsViewData.stgkz,
sem: this.propsViewData.sem,
verband: this.propsViewData.verband,
gruppe: this.propsViewData.gruppe,
}
});
},
updateRange(rangeInterval) {
this.$api
.call(ApiLvPlan.studiensemesterDateInterval(
rangeInterval.end.startOf('week').toISODate()
))
.then(res => {
this.studiensemester_kurzbz = res.data.studiensemester_kurzbz;
this.studiensemester_start = res.data.start;
this.studiensemester_ende = res.data.ende;
});
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiLvPlan.eventsStgOrg(start.toISODate(), end.toISODate(), this.propsViewData.stgkz, this.propsViewData.sem, this.propsViewData.verband, this.propsViewData.gruppe)),
//local for test
/* this.$api.call(ApiLvPlan.eventsStgOrg(start.toISODate(), end.toISODate(), this.stgkz, this.sem, this.verband, this.gruppe))*/
];
},
backToDropdown(){
this.$router.push({
name: "OverviewLvPlan",
});
}
},
created(){
this.$api
.call(ApiAuthinfo.getAuthInfo())
.then(res => {
this.uid = res.data.uid;
this.isMitarbeiter = res.data.isMitarbeiter;
this.isStudent = res.data.isStudent;
});
if(this.propsViewData.stgkz) {
this.$api
.call(ApiLvPlan.getStudiengaenge())
.then(result => {
const currentStg = result.data.find(
item => item.studiengang_kz == this.propsViewData.stgkz
);
this.currentStgBezeichnung = currentStg.kurzbzlang + " - " + currentStg.bezeichnung;
})
.catch(this.$fhcAlert.handleSystemError);
}
},
template: `
<div class="cis-lvplan-stg-org d-flex flex-column h-100">
<h2>{{ $p.t('LvPlan/headerLvPlanLvVerband') }}</h2>
<p v-if="propsViewData.stgkz"><span><strong>{{currentStgBezeichnung}}</strong></span>
<span v-if="propsViewData.sem" style="margin-left:10px;"> Semester: {{propsViewData.sem}} </span>
<span v-if="propsViewData.verband" style="margin-left:10px;"> Verband: {{propsViewData.verband}} </span>
<span v-if="propsViewData.gruppe" style="margin-left:10px;"> Gruppe: {{propsViewData.gruppe}} </span>
<button class="py-1 btn btn-outline-secondary" :title="this.$p.t('LvPlan', 'backToDropdown')" style="margin-left:10px;"> <i class="fa fa-arrow-left fa-xs" @click="backToDropdown"></i></button>
</p>
<p v-else>{{ $p.t('LvPlan/noStgProvided') }}</p>
<fhc-calendar
ref="calendar"
:timezone="viewData.timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@update:range="updateRange"
class="responsive-calendar"
>
<div
v-if="downloadLinks"
class="d-flex gap-1 justify-items-start"
>
<div v-for="{ title, icon, link } in downloadLinks">
<a
:href="link"
:aria-label="title"
class="py-1 btn btn-outline-secondary"
>
<div class="d-flex flex-column">
<i aria-hidden="true" :class="icon"></i>
<span style="font-size:.5rem">{{ title }}</span>
</div>
</a>
</div>
</div>
</fhc-calendar>
</div>
`,
};
@@ -31,7 +31,7 @@ export default {
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
].join(": "));
} else {
} else {;
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
+34 -66
View File
@@ -3,20 +3,15 @@ import DashboardAdminEdit from "./Admin/Edit.js";
import DashboardAdminWidgets from "./Admin/Widgets.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 {
name: 'DashboardAdmin',
components: {
DashboardAdminEdit,
DashboardAdminWidgets,
DashboardAdminPresets,
DashboardAdminPresets
},
provide() {
return {
adminMode: true,
widgetsSetup: Vue.computed(() => this.dashboards[this.current] ? this.dashboards[this.current].widgetSetup : null)
adminMode: true
};
},
data() {
@@ -27,6 +22,9 @@ export default {
};
},
computed: {
apiurl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
},
dashboard() {
return this.dashboards.find(el => el.dashboard_id == this.current);
}
@@ -37,50 +35,33 @@ export default {
BsPrompt.popup('New Dashboard name').then(
name => {
_name = name;
const params = {
return axios.post(this.apiurl + '/Dashboard/create', {
dashboard_kurzbz: name
};
return this.$api
.call(ApiDashboardBoard.add(params))
.then(response =>{
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
let newDashboard = {
dashboard_id: response.data,
dashboard_kurzbz: _name,
beschreibung: ''
};
this.dashboards.push(newDashboard);
this.current = newDashboard.dashboard_id;
})
.catch(this.$fhcAlert.handleSystemError);
});
})
}
).then(res => {
let newDashboard = {
dashboard_id: res.data.retval,
dashboard_kurzbz: _name,
beschreibung: ''
};
this.dashboards.push(newDashboard);
this.current = newDashboard.dashboard_id;
}).catch(err => err !== undefined ? console.error('ERROR:', err) : 0);
},
dashboardUpdate(dashboard) {
return this.$api
.call(ApiDashboardBoard.update(dashboard))
.then(response =>{
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
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);
// TODO(chris): Loading or message
axios.post(this.apiurl + '/Dashboard/update', dashboard).then(() => {
let old = this.dashboards.find(el => el.dashboard_id == dashboard.dashboard_id);
old.dashboard_kurzbz = dashboard.dashboard_kurzbz;
old.beschreibung = dashboard.beschreibung;
}).catch(err => console.error('ERROR:', err));
},
dashboardDelete(dashboard_id) {
return this.$api
.call(ApiDashboardBoard.delete(dashboard_id))
.then(response => {
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);
});
axios.post(this.apiurl + '/Dashboard/delete', {dashboard_id}).then(() => {
this.current = -1;
this.dashboards = this.dashboards.filter(el => el.dashboard_id != dashboard_id);
}).catch(err => console.error('ERROR:', err));
},
assignWidgets(widgets) {
this.widgets = widgets;
@@ -91,35 +72,22 @@ export default {
}
},
created() {
this.$api
.call(ApiDashboardBoard.list())
.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);
axios.get(this.apiurl + '/Dashboard').then(res => {
this.dashboards = res.data.retval;
}).catch(err => console.error('ERROR:', err));
},
template: `<div class="dashboard-admin">
<div class="input-group">
<label for="dashboard-select" class="input-group-text">Dashboard:</label>
<select id="dashboard-select" class="form-select" v-model="current">
<label for="dashbaord-select" class="input-group-text">Dashboard:</label>
<select id="dashbaord-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>
</select>
<button class="btn btn-outline-secondary" type="button" @click="dashboardAdd"><i class="fa-solid fa-plus"></i></button>
</div>
<div v-if="dashboard">
<ul class="nav nav-tabs mt-3" role="tablist">
<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">{{this.$p.t('ui', 'bearbeiten')}}</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">Edit</button>
</li>
<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>
@@ -133,7 +101,7 @@ export default {
<dashboard-admin-edit v-bind="dashboard" :key="dashboard.dashboard_id" @change="dashboardUpdate($event)" @delete="dashboardDelete($event)"></dashboard-admin-edit>
</div>
<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" @assign-widgets="assignWidgets"></dashboard-admin-widgets>
<dashboard-admin-widgets :key="dashboard.dashboard_id" :dashboard_id="dashboard.dashboard_id" :widgets="widgets" @change="test" @assign-widgets="assignWidgets"></dashboard-admin-widgets>
</div>
<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>
+3 -4
View File
@@ -18,8 +18,7 @@ export default {
},
methods: {
sendDelete() {
BsConfirm.popup(this.$p.t('ui', 'confirm_delete') + " " + this.$p.t('ui', 'deleteInfo'))
.then(() => this.$emit('delete', this.dashboard_id)).catch();
BsConfirm.popup('Sure?').then(() => this.$emit('delete', this.dashboard_id)).catch();
}
},
template: `<div class="dashboard-admin-edit px-3">
@@ -32,8 +31,8 @@ export default {
<textarea id="dashboard-admin-edit-beschreibung" class="form-control" v-model="desc"></textarea>
</div>
<div>
<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})">{{this.$p.t('ui', 'btnAktualisieren')}}</button>
<button class="btn btn-danger" @click="sendDelete">Delete</button>
<button class="btn btn-primary" @click="$emit('change', {dashboard_id,dashboard_kurzbz:kurzbz,beschreibung:desc})">Update</button>
</div>
</div>`
}
+89 -111
View File
@@ -1,7 +1,6 @@
import DashboardSection from "../Section.js";
import DashboardWidgetPicker from "../Widget/Picker.js";
import ObjectUtils from "../../../helpers/ObjectUtils.js";
import ApiDashboardPreset from "../../../api/factory/dashboard/preset.js";
export default {
components: {
@@ -18,6 +17,9 @@ export default {
tmpLoading: ''
}),
computed: {
apiurl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
},
pickerWidgets() {
return this.widgets.filter(widget => widget.allowed);
}
@@ -26,7 +28,6 @@ export default {
widgetAdd(section_name, widget) {
this.$refs.widgetpicker.getWidget().then(widget_id => {
widget.widget = widget_id;
widget.id = 'loading_' + String((new Date()).valueOf());
delete widget.custom;
widget.preset = 1;
let loading = {...widget};
@@ -35,153 +36,130 @@ export default {
if (section.name == section_name)
section.widgets.push(loading);
});
const params = {
dashboard: this.dashboard,
axios.post(this.apiurl + '/Config/addWidgetsToPreset', {
db: this.dashboard,
funktion_kurzbz: section_name,
widget
};
return this.$api
.call(ApiDashboardPreset.addWidget(params))
.then(result => {
let newId = result.data;
widget.id = newId;
widget.custom = 1;
this.sections.forEach(section => {
if (section.name == section_name) {
section.widgets.splice(section.widgets.indexOf(loading),1);
section.widgets.push(widget);
}
});
this.funktionen.forEach(funktion => {
if(funktion.funktion_kurzbz === section_name && funktion.has_preset < 1) {
funktion.has_preset = 1;
}
});
})
.catch(this.$fhcAlert.handleSystemError);
})
.catch(() => {});
widgets: [widget]
}).then(result => {
let newId = Object.keys(result.data.retval.data[section_name].widgets).pop();
widget.id = newId;
widget.custom = 1;
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) {
payload = payload[section_name];
for (var k in payload) {
const section = this.sections.find(section => section.name == section_name);
for (var wid in section.widgets) {
if (section.widgets[wid].id == k) {
payload[k] = ObjectUtils.mergeDeep(section.widgets[wid], payload[k]);
// NOTE(chris): remove internal props
for (var prop of ['_x', '_y', '_w', '_h', 'index', 'id'])
if (payload[k][prop])
delete payload[k][prop];
for (var i in this.sections) {
if (this.sections[i].name == section_name) {
for (var wid in this.sections[i].widgets) {
if (this.sections[i].widgets[wid].id == k) {
payload[k] = ObjectUtils.mergeDeep(this.sections[i].widgets[wid], payload[k]);
// NOTE(chris): remove internal props
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1})
if (payload[k][prop])
delete payload[k][prop];
break;
}
}
break;
}
}
payload[k].widgetid = k;
delete payload[k].custom;
}
this.$api
.call(Object.entries(payload).map(([key, widget]) => [
key,
ApiDashboardPreset.addWidget({
dashboard: this.dashboard,
funktion_kurzbz: section_name,
widget
})
]))
.then(result => {
this.sections.forEach(section => {
if (section.name == section_name) {
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];
section.widgets[i].custom = 1;
}
});
}
});
})
.catch(this.$fhcAlert.handleSystemError);
axios.post(this.apiurl + '/Config/addWidgetsToPreset', {
db: this.dashboard,
funktion_kurzbz: section_name,
widgets: payload
}).then(() => {
this.sections.forEach(section => {
if (section.name == section_name) {
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];
section.widgets[i].custom = 1;
}
});
}
});
}).catch(error => {
// TODO(chris): revert placement on failure
console.error('ERROR: ', error);
alert('ERROR: ' + error.response.data.retval);
});
},
widgetRemove(section_name, id) {
const params = {
axios.post(this.apiurl + '/Config/removeWidgetFromPreset', {
db: this.dashboard,
funktion_kurzbz: section_name,
widgetid: id
};
return this.$api
.call(ApiDashboardPreset.removeWidget(params))
.then(result => {
this.sections.forEach(section => {
if (section.name == section_name)
section.widgets = section.widgets.filter(widget => widget.id != id);
});
})
.catch(this.$fhcAlert.handleSystemError);
}).then(() => {
this.sections.forEach(section => {
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);
});
},
loadSections(evt) {
let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value);
this.sections = [];
this.tmpLoading = funktionen.join('###');
const params = {
axios.get(this.apiurl + '/Config/presetBatch', {params: {
db: this.dashboard,
funktionen
};
return this.$api
.call(ApiDashboardPreset.getBatch(params))
.then(result => {
if (this.tmpLoading !== funktionen.join('###'))
return; // NOTE(chris): prevent race condition
for (var section in result.data) {
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
});
}}).then(res => {
if (this.tmpLoading !== funktionen.join('###'))
return; // NOTE(chris): prevent race condition
for (var section in res.data.retval) {
let widgets = [];
for (var wid in res.data.retval[section]) {
res.data.retval[section][wid].id = wid;
res.data.retval[section][wid].custom = 1;
widgets.push(res.data.retval[section][wid]);
}
})
.catch(this.$fhcAlert.handleSystemError);
},
loadFunktionen() {
this.$api
.call(ApiDashboardPreset.list(this.dashboard))
.then(result => {
this.funktionen = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
this.sections.push({
name: section,
widgets
});
}
}).catch(err => console.error('ERROR:', err));
}
},
created() {
this.loadFunktionen();
axios.get(this.apiurl + '/Config/funktionen').then(res => {
this.funktionen = {general: 'GENERAL'};
res.data.retval.forEach(funktion => {
this.funktionen[funktion.funktion_kurzbz] = funktion.beschreibung;
});
}).catch(err => console.error('ERROR:', err));
},
watch: {
dashboard() {
// TODO(chris): this should be done without a watcher
this.loadSections({target:this.$refs.funktionenList});
this.loadFunktionen();
}
},
template: `<div class="dashboard-admin-presets">
<div class="row">
<div class="col-3">
<select ref="funktionenList" style="height:30em" class="form-control" multiple @input="loadSections">
<option
v-for="funktion in funktionen"
:key="funktion.funktion_kurzbz"
:value="funktion.funktion_kurzbz"
:class="(funktion.has_preset > 0) ? 'fw-bold' : ''"
>{{ funktion.beschreibung }}</option>
<option v-for="name,id in funktionen" :key="id" :value="id">{{ name }}</option>
</select>
</div>
<div class="col-9">
+20 -13
View File
@@ -1,5 +1,3 @@
import ApiDashboardWidget from "../../../api/factory/dashboard/widget.js";
export default {
emits: [
"change",
@@ -9,25 +7,34 @@ export default {
dashboard_id: Number,
widgets: Array
},
computed: {
apiurl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
}
},
methods: {
sendChange(widget_id) {
let allow = !this.widgets.find(el => el.widget_id == widget_id).allowed;
this.$api
.call(ApiDashboardWidget.setAllowed(this.dashboard_id, widget_id, allow))
.catch(this.$fhcAlert.handleSystemError);
axios.post(this.apiurl + '/Widget/setAllowed', {
dashboard_id: this.dashboard_id,
widget_id,
action: allow ? 'add' : 'delete'
}).catch(err => console.error('ERROR: ' + err));
}
},
created() {
this.$api
.call(ApiDashboardWidget.list(this.dashboard_id))
.then(result => {
this.$emit('assignWidgets', result.data.map(el => ({
axios.get(this.apiurl + '/Widget/getAll', {
params:{
dashboard_id: this.dashboard_id
}
}).then(
result => {
this.$emit('assignWidgets', result.data.retval.map(el => ({
...el,
allowed: !!el.allowed
...{setup:JSON.parse(el.setup),arguments:JSON.parse(el.arguments),allowed:!!el.allowed}
})));
})
.catch(this.$fhcAlert.handleSystemError);
}
).catch(err => console.error('ERROR:', err));
},
template: `
<div class="dashboard-admin-widgets">
+138 -105
View File
@@ -2,8 +2,7 @@ import DashboardSection from "./Section.js";
import DashboardWidgetPicker from "./Widget/Picker.js";
import ObjectUtils from "../../helpers/ObjectUtils.js";
import ApiDashboardWidget from '../../api/factory/dashboard/widget.js';
import ApiDashboardUser from '../../api/factory/dashboard/user.js';
import ApiDashboard from '../../api/factory/cis/dashboard.js';
export default {
name: 'Dashboard',
@@ -21,147 +20,181 @@ export default {
type: Object,
required: true,
validator(value) {
return value && value.name && value.timezone
return value && value.name && value.uid && value.timezone
}
}
},
data() {
return {
widgets: [],
originalWidgets: {},
widgetsSetup: null,
editMode: false
sections: [],
widgets: null,
editMode: false,
viewDataInternal: this.viewData
}
},
provide() {
return {
editMode: Vue.computed(()=>this.editMode),
widgetsSetup: Vue.computed(() => this.widgetsSetup),
widgetsSetup: Vue.computed(() => this.widgets),
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: {
widgetAdd(section_name, widget) {
// TODO(chris): remove section_name? (change order of params => get rid of it)
this.$refs.widgetpicker
.getWidget()
.then(widget_id => {
widget.widget = widget_id;
widget.id = 'loading_' + String((new Date()).valueOf());
let loading = { ...widget };
loading.loading = true;
this.widgets.push(loading);
this.$api
.call(ApiDashboardUser.addWidget(this.dashboard, widget))
.then(result => {
widget.id = result.data;
this.widgets.splice(this.widgets.indexOf(loading), 1);
this.widgets.push(widget);
this.originalWidgets[widget.id] = structuredClone(ObjectUtils.deepToRaw(widget));
})
.catch(this.$fhcAlert.handleSystemError);
})
.catch(() => {});
if (this.widgets === null) {
axios.get(this.apiurl + '/Widget/getWidgetsForDashboard', {params:{
db: this.dashboard
}}).then(res => {
res.data.retval.forEach(widget => {
widget.arguments = JSON.parse(widget.arguments);
widget.setup = JSON.parse(widget.setup);
});
this.widgets = res.data.retval;
}).catch(err => console.error('ERROR:', err));
}
this.$refs.widgetpicker.getWidget().then(widget_id => {
widget.widget = widget_id;
widget.id = 'loading_' + String((new Date()).valueOf());
let loading = {...widget};
loading.loading = true;
this.sections.forEach(section => {
if (section.name == section_name)
section.widgets.push(loading);
});
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) {
payload = payload[section_name];
for (var k in payload) {
for (var wid in this.widgets) {
if (this.widgets[wid].id == k) {
payload[k] = ObjectUtils.mergeDeep(this.widgets[wid], payload[k]);
// NOTE(chris): remove internal props
for (var prop of ['_x','_y','_w','_h','index','id','preset'])
if (payload[k][prop])
delete payload[k][prop];
for (var i in this.sections) {
if (this.sections[i].name == section_name) {
for (var wid in this.sections[i].widgets) {
if (this.sections[i].widgets[wid].id == k) {
payload[k] = ObjectUtils.mergeDeep(this.sections[i].widgets[wid], payload[k]);
// NOTE(chris): remove internal props
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1,preset:1})
if (payload[k][prop])
delete payload[k][prop];
break;
}
}
break;
}
}
payload[k].widgetid = k;
}
this.$api
.call(Object.entries(payload).map(([key, widget]) => [
key,
ApiDashboardUser.addWidget(this.dashboard, widget)
]))
.then(result => {
const failed = result
.filter(o => o.status == 'rejected')
.map(o => o.reason.config.errorHeader);
this.widgets.forEach((widget, i) => {
if (failed.includes(widget.id)) {
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
/** NOTE(chris): if you wanna hide or unhide a
* preset and it fails: switch around the hidden
* value to revert it properly (checkboxes can't
* really handle it otherwise)
*/
if (payload[widget.id].hidden !== undefined) {
this.widgets[i].hidden = payload[widget.id].hidden;
this.$nextTick(() => {
this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id]));
});
axios.post(this.apiurl + '/Config/addWidgetsToUserOverride', {
db: this.dashboard,
funktion_kurzbz: section_name,
widgets: payload
}).then(() => {
this.sections.forEach(section => {
if (section.name == section_name) {
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];
}
} else if (payload[widget.id]) {
payload[widget.id].id = widget.id;
payload[widget.id].index = widget.index;
this.widgets[i] = payload[widget.id];
this.originalWidgets[widget.id] = structuredClone(ObjectUtils.deepToRaw(this.widgets[i]));
}
});
})
.catch(this.$fhcAlert.handleSystemError);
});
}
});
}).catch(error => {
// TODO(chris): revert placement on failure
console.error('ERROR: ', error);
alert('ERROR: ' + error.response.data.retval);
});
},
widgetRemove(section_name, id) {
this.$api
.call(ApiDashboardUser.removeWidget(this.dashboard, id))
.then(() => {
this.widgets = this.widgets.filter(widget => widget.id != id);
})
.catch(this.$fhcAlert.handleSystemError);
axios.post(this.apiurl + '/Config/removeWidgetFromUserOverride', {
db: this.dashboard,
funktion_kurzbz: section_name,
widgetid: id
}).then(() => {
this.sections.forEach(section => {
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() {
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));
this.$api
.call(ApiDashboardWidget.listAllowed(this.dashboard))
.then(res => {
this.widgetsSetup = res.data;
})
.catch(this.$fhcAlert.handleSystemError);
this.$api
.call(ApiDashboardUser.get(this.dashboard))
.then(res => {
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 {
axios.get(this.apiurl + '/Config', {params:{
db: this.dashboard
}}).then(res => {
for (var name in res.data.retval) {
let widgets = [];
let remove = [];
for (var wid in res.data.retval[name].widgets) {
res.data.retval[name].widgets[wid].id = wid;
if (res.data.retval[name].widgets[wid].custom || res.data.retval[name].widgets[wid].preset)
widgets.push(res.data.retval[name].widgets[wid]);
else
remove.push(wid);
}
}
remove.forEach(wid => this.widgetRemove('general', wid));
this.widgets = widgets;
})
.catch(this.$fhcAlert.handleSystemError);
this.sections.push({
name: name,
widgets: widgets
});
remove.forEach(wid => this.widgetRemove(name, wid));
}
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: `
<div class="core-dashboard">
<h3>
{{ $p.t('global/personalGreeting', [ viewData?.name ]) }}
<h3 v-show="viewDataInternal?.name">
{{ $p.t('global/personalGreeting', [ viewDataInternal?.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>
</h3>
<dashboard-section :seperator="0" name="general" :widgets="widgets" @widgetAdd="widgetAdd" @widgetUpdate="widgetUpdate" @widgetRemove="widgetRemove"></dashboard-section>
<dashboard-widget-picker ref="widgetpicker" :widgets="widgetsSetup"></dashboard-widget-picker>
<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-widget-picker ref="widgetpicker" :widgets="widgets"></dashboard-widget-picker>
</div>`
}
+3 -17
View File
@@ -1,5 +1,5 @@
import BsModal from "../Bootstrap/Modal.js";
import { useCachedWidgetLoader } from "../../composables/Dashboard/CachedWidgetLoader.js";
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
import HeightTransition from "../Tranistion/HeightTransition.js";
export default {
@@ -70,14 +70,6 @@ export default {
ready() {
return this.component && this.arguments !== null;
},
visible: {
get() {
return !this.hidden;
},
set(value) {
this.$emit('remove', this.hidden);
}
}
},
methods: {
unpin(){
@@ -150,14 +142,8 @@ export default {
this.isLoading = false;
},
},
setup() {
const { actions } = useCachedWidgetLoader();
return {
loadWidget: actions.load
};
},
async created() {
this.widget = await this.loadWidget(this.id);
this.widget = await CachedWidgetLoader.loadWidget(this.id);
let component = (await import(this.widget.setup.file)).default;
this.$options.components["widget" + this.widget.widget_id] = component;
this.component = "widget" + this.widget.widget_id;
@@ -199,7 +185,7 @@ export default {
</a>
<Transition>
<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" v-model="visible" :value="true">
<input class="form-check-input ms-0" type="checkbox" role="switch" aria-label="toggle widget" id="flexSwitchCheckChecked" :checked="!hidden" @input="$emit('remove', hidden)">
</div>
</Transition>
</div>
+7 -14
View File
@@ -1,7 +1,7 @@
import BsConfirm from "../Bootstrap/Confirm.js";
import DropGrid from '../Drop/Grid.js'
import DashboardItem from "./Item.js";
import { useCachedWidgetLoader } from "../../composables/Dashboard/CachedWidgetLoader.js";
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
import WidgetIcon from "./Widget/WidgetIcon.js"
export default {
@@ -125,23 +125,23 @@ export default {
},
checkResizeLimit(item, w, h) {
// NOTE(chris): widgets needs to be loaded for this to work
let widget = this.widgetState[item.widget];
let widget = CachedWidgetLoader.getWidget(item.widget);
if (widget) {
let minmaxW = { ...widget.setup.width };
let minmaxW = widget.setup.width;
if (minmaxW.max)
minmaxW.min = minmaxW.min || 1;
else
minmaxW = { min: minmaxW, max: minmaxW };
minmaxW = {min:minmaxW,max:minmaxW};
if (w < minmaxW.min)
w = minmaxW.min;
if (w > minmaxW.max)
w = minmaxW.max;
let minmaxH = { ...widget.setup.height };
let minmaxH = widget.setup.height;
if (minmaxH.max)
minmaxH.min = minmaxH.min || 1;
else
minmaxH = { min: minmaxH, max: minmaxH };
minmaxH = {min:minmaxH,max:minmaxH};
if (h < minmaxH.min)
h = minmaxH.min;
if (h > minmaxH.max)
@@ -151,7 +151,7 @@ export default {
},
removeWidget(item, revert) {
if (item.custom) {
BsConfirm.popup(this.$p.t('dashboard', 'alert_deleteWidget')).then(() => this.$emit('widgetRemove', this.name, item.id));
BsConfirm.popup('Are you sure you want to delete this widget?').then(() => this.$emit('widgetRemove', this.name, item.id));
} else {
let update = {};
update[item.id] = { hidden: !revert };
@@ -199,13 +199,6 @@ export default {
this.$emit('widgetUpdate', this.name, payload);
}
},
setup() {
const { state: widgetState } = useCachedWidgetLoader();
return {
widgetState
};
},
mounted() {
let self = this;
let cont = self.$refs.container;
+3 -4
View File
@@ -170,7 +170,6 @@ export default {
return this.$attrs.modelValue;
},
set(v) {
this.clearValidationForThisName()
if (!this.$attrs.hasOwnProperty('modelValue'))
this.modelValueDummy = v;
this.$emit('update:modelValue', v);
@@ -243,9 +242,9 @@ export default {
template: `
<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>
<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="$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="$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="clearValidationForThisName(); $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>
<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)">
<slot></slot>
</select>
<component
+17 -30
View File
@@ -18,7 +18,6 @@
import CoreSearchbar from "../searchbar/searchbar.js";
import NavLanguage from "../navigation/Language.js";
import VerticalSplit from "../verticalsplit/verticalsplit.js";
import HorizontalSplit from "../horizontalsplit/horizontalsplit.js";
import AppMenu from "../AppMenu.js";
import AppConfig from "../AppConfig.js";
import StvVerband from "./Studentenverwaltung/Verband.js";
@@ -38,7 +37,6 @@ export default {
CoreSearchbar,
NavLanguage,
VerticalSplit,
HorizontalSplit,
AppMenu,
AppConfig,
StvVerband,
@@ -237,10 +235,6 @@ export default {
}
}
}
},
sidebarCollapsed(newVal) {
if(newVal) this.$refs.hSplit.collapseLeft()
else this.$refs.hSplit.showBoth()
}
},
methods: {
@@ -638,30 +632,23 @@ export default {
</app-menu>
</div>
</aside>
<horizontal-split ref="hSplit" :defaultRatio="[15, 85]">
<template #left>
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 w-100">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<stv-verband :preselectedKey="studiengangKz ? '' + studiengangKz : null" :endpoint="verbandEndpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
<stv-studiensemester v-model:studiensemester-kurzbz="studiensemesterKurzbz" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
</nav>
</template>
<template #right>
<main>
<vertical-split :defaultRatio="[50, 50]">
<template #top>
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filterActive="handleCustomFilter"></stv-list>
</template>
<template #bottom>
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
</template>
</vertical-split>
</main>
</template>
</horizontal-split>
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<stv-verband :preselectedKey="studiengangKz ? '' + studiengangKz : null" :endpoint="verbandEndpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
<stv-studiensemester v-model:studiensemester-kurzbz="studiensemesterKurzbz" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
</nav>
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
<vertical-split>
<template #top>
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filterActive="handleCustomFilter"></stv-list>
</template>
<template #bottom>
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
</template>
</vertical-split>
</main>
</div>
</div>
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
@@ -74,16 +74,6 @@ export default {
],
'abschlussdokument_lehrgaenge.xml.php': [
'AbschlussdokumentLehrgaenge'
],
'microcredential.xml.php' : [
'microcredentialzertifikat_1',
'microcredentialzertifikat_2',
'microcredentialzertifikat_3',
'microcredentialzertifikat_4',
'microcredential_1',
'microcredential_2',
'microcredential_3',
'microcredential_4',
]
},
documentDropdownObject: {}
@@ -54,7 +54,10 @@ export default {
start_date: this.dataSem.start,
end_date: this.dataSem.ende,
group_consecutiveHours: true,
dbStundenplanTable: this.dbStundenplanTable})
dbStundenplanTable: this.dbStundenplanTable,
showLvsStundenplan: true
})
),
ajaxResponse: (url, params, response) => {
return response.data;
@@ -14,9 +14,6 @@ export default {
inject: {
defaultSemester: {
from: 'defaultSemester'
},
currentSemester: {
from: 'currentSemester'
}
},
computed: {
@@ -98,9 +95,8 @@ export default {
this.formData.themenbereich = null;
this.formData.projekttyp_kurzbz = null;
this.formData.firma = null;
// dont reset these form fields for UX reasons
// this.formData.lehrveranstaltung_id = null;
// this.formData.lehreinheit_id = null;
this.formData.lehrveranstaltung_id = null;
this.formData.lehreinheit_id = null;
this.formData.beginn = null;
this.formData.ende = null;
this.formData.freigegeben = true;
@@ -113,7 +109,7 @@ export default {
getFormData(newProjektarbeit, studiensemester_kurzbz, additional_lehrveranstaltung_id) {
this.additional_lehrveranstaltung_id = additional_lehrveranstaltung_id;
this.studiensemester = studiensemester_kurzbz || this.currentSemester;
this.studiensemester = studiensemester_kurzbz || this.defaultSemester;
this.newProjektarbeit = newProjektarbeit;
this.$api
@@ -121,6 +121,7 @@ export default {
height: 'auto',
minHeight: '100',
selectableRows: true,
selectableRows: 1,
index: 'betreuer_id',
persistence:{
columns: true, //persist column layout
@@ -71,6 +71,7 @@ export default {
:mitarbeiter_uid="this.mitarbeiter_uid"
typeHeader="mitarbeiter"
:domain="config.domain"
fotoEditable
@redirectToLeitung="handleSelection"
>
<template #uid>{{tile_MaUid}}</template>
+2 -2
View File
@@ -280,7 +280,7 @@ export const CoreFilterCmpt = {
});
}
if (tabulatorOptions.selectable || tabulatorOptions.selectableRows || (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length))
if (tabulatorOptions.selectable || (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length))
this.tabulatorHasSelector = true;
if (this.idField) {
@@ -358,7 +358,7 @@ export const CoreFilterCmpt = {
}
},
_updateTabulator() {
this.tabulatorHasSelector = this.tabulatorOptions.selectable || this.tabulatorOptions.selectableRows || this.filteredColumns.filter(el => el.formatter == 'rowSelection').length;
this.tabulatorHasSelector = this.tabulatorOptions.selectable || this.filteredColumns.filter(el => el.formatter == 'rowSelection').length;
this.tabulator.setColumns(this.filteredColumns);
this.tabulator.setData(this.filteredData);
this._setHeaderFilter()
@@ -1,147 +0,0 @@
export default {
name: 'HorizontalSplit',
props: {
defaultRatio: {
type: Array,
default: () => [50, 50]
}
},
data: function () {
return {
availWidth: 0,
leftwidth: 0,
rightwidth: 0,
mousePosX: 0,
resize: false,
hsplitterOffset: 0,
selfOffsetLeft: 0
};
},
template: `
<div ref="horizontalsplit" class="horizontalsplit-container">
<div ref="leftpanel" class="horizontalsplitted"
:style="{ width: this.leftwidthcss }">
<slot name="left">
<p>Left Panel</p>
</slot>
</div>
<div ref="hsplitter" class="horizontalsplitter"
:class="this.leftOrRightClass" @mousedown="this.dragStart">
<div class="splitactions horizontal" :class="this.leftOrRightClass">
<span @click="this.collapseRight" class="splitaction">
<i class="fas fa-angle-right text-muted"></i>
</span>
<span @dblclick="this.showBoth" class="splitaction resize">
<i class="fas fa-grip-lines-vertical text-muted"></i>
</span>
<span @click="this.collapseLeft" class="splitaction">
<i class="fas fa-angle-left text-muted"></i>
</span>
</div>
</div>
<div ref="rightpanel" class="horizontalsplitted"
:style="{ width: this.rightwidthcss }">
<slot name="right">
<p/>
</slot>
</div>
</div>
`,
mounted: function () {
this.calcWidths();
this.trackHorizontalSplitterOffsetLeft();
window.addEventListener('resize', this.calcWidths);
},
updated: function () {
this.trackHorizontalSplitterOffsetLeft();
},
beforeDestroy: function () {
window.removeEventListener('resize', this.calcWidths);
},
methods: {
calcWidths: function () {
var oldavailWidth = this.availWidth;
this.selfOffsetLeft = this.$refs.horizontalsplit.offsetLeft;
this.availWidth = this.$refs.horizontalsplit.offsetWidth - this.$refs.hsplitter.offsetWidth;
if ((this.leftwidth === 0 && this.rightwidth === 0) || oldavailWidth === 0) {
this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100));
} else {
this.leftwidth = Math.floor(((this.leftwidth * 100) / oldavailWidth) / 100 * this.availWidth);
}
this.rightwidth = this.availWidth - this.leftwidth;
},
collapseLeft: function () {
this.calcWidths();
this.leftwidth = 0;
this.rightwidth = this.availWidth;
},
collapseRight: function () {
this.calcWidths();
this.leftwidth = this.availWidth;
this.rightwidth = 0;
},
showBoth: function () {
this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100));
this.rightwidth = this.availWidth - this.leftwidth;
},
isCollapsed: function () {
if (this.leftwidth === 0) {
return 'left';
} else if (this.rightwidth === 0) {
return 'right';
} else {
return false;
}
},
dragStart: function (e) {
e.preventDefault();
e.stopPropagation();
window.addEventListener('mouseup', this.dragEnd);
window.addEventListener('mousemove', this.drag);
this.resize = true;
this.mousePosX = e.clientX;
},
drag: function (e) {
if (!this.resize) {
return;
}
e.preventDefault();
e.stopPropagation();
var offsetX = e.clientX - this.mousePosX;
this.leftwidth = this.leftwidth + offsetX;
if (this.leftwidth < 0) {
this.leftwidth = 0;
}
if (this.leftwidth > this.availWidth) {
this.leftwidth = this.availWidth;
}
this.rightwidth = this.availWidth - this.leftwidth;
this.mousePosX = e.clientX;
},
dragEnd: function (e) {
e.preventDefault();
e.stopPropagation();
window.removeEventListener('mousemove', this.drag);
window.removeEventListener('mouseup', this.dragEnd);
this.resize = false;
this.mousePosX = e.clientX;
},
trackHorizontalSplitterOffsetLeft: function () {
this.hsplitterOffset = this.$refs.hsplitter.offsetLeft;
}
},
computed: {
leftOrRightClass: function () {
return ((this.hsplitterOffset - this.selfOffsetLeft) <= Math.floor(this.availWidth / 2))
? 'left'
: 'right';
},
leftwidthcss: function () {
return this.leftwidth + 'px';
},
rightwidthcss: function () {
return this.rightwidth + 'px';
}
}
};
@@ -1,11 +1,5 @@
export default {
name: 'VerticalSplit',
props: {
defaultRatio: {
type: Array,
default: () => [50, 50]
}
},
data: function() {
return {
availHeight: 0,
@@ -56,22 +50,17 @@ export default {
updated: function() {
this.trackVerticalSplitterOffsetTop();
},
beforeDestroy: function () {
window.removeEventListener('resize', this.calcHeights);
},
methods: {
calcHeights: function() {
var windowheight = window.innerHeight;
var oldavailHeight = this.availHeight;
this.selfOffsetTop = this.$refs.verticalsplit.offsetTop;
this.availHeight = windowheight - this.selfOffsetTop - this.$refs.vsplitter.offsetHeight;
if( (this.topheight === 0 && this.bottomheight === 0) || oldavailHeight === 0 ) {
this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100));
this.topheight = Math.floor(this.availHeight/2);
} else {
this.topheight = Math.floor( ((((this.topheight * 100) / oldavailHeight) / 100) * this.availHeight) );
}
this.bottomheight = this.availHeight - this.topheight;
},
collapseTop: function() {
@@ -85,8 +74,8 @@ export default {
this.bottomheight = 0;
},
showBoth: function() {
this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100));
this.bottomheight = this.availHeight - this.topheight
this.topheight = Math.floor(this.availHeight/2);
this.bottomheight = Math.floor(this.availHeight/2);
},
isCollapsed: function() {
if( this.topheight === 0 ) {
@@ -1,36 +1,29 @@
import ApiWidget from "../../api/factory/dashboard/widget.js";
let __widgets = {};
let __widgetsStarted = {};
let __path = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard/Widget';
const promises = Vue.ref([]);
const stateRef = Vue.ref([]);
const state = Vue.readonly(stateRef);
export default {
getWidget(id) {
return __widgets[id];
},
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!');
export function useCachedWidgetLoader() {
const $api = Vue.inject('$api');
const $fhcAlert = Vue.inject('$fhcAlert');
function load(id) {
if (state.value[id])
return Promise.resolve(state.value[id]);
if (!promises.value[id])
promises.value[id] = new Promise((resolve, reject) => {
$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];
__widgetsStarted[id] = new Promise((resolve, reject) => {
axios.get(__path, {params:{id}}).then(res => {
__widgets[id] = res.data.retval;
__widgetsStarted[id] = undefined;
resolve(__widgets[id]);
}).catch(error => reject(error.response.data.retval.error));
});
return __widgetsStarted[id];
},
setPath(path) {
__path = path;
}
return {
state,
actions: {
load
}
};
}
+3 -14
View File
@@ -111,7 +111,7 @@ export function useEventLoader(rangeInterval, getPromiseFunc) {
return mergePromiseArr(getPromiseFunc(start, end), result);
};
const reload = () => {
Vue.watchEffect(() => {
const range = Vue.toValue(rangeInterval);
if (!(range instanceof luxon.Interval))
return;
@@ -132,18 +132,7 @@ export function useEventLoader(rangeInterval, getPromiseFunc) {
}
})
});
};
})
Vue.watchEffect(reload);
const reset = () => {
loading_id = 0;
events.value = [];
loadingEvents.value = [];
eventsLoaded.splice(0, eventsLoaded.length);
reload();
}
return { events: allEvents, lv, reset }
return { events: allEvents, lv }
}
+29 -70
View File
@@ -1,72 +1,31 @@
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[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;
}, {});
}
/**
* 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
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
mergeDeep(...objects) {
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[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;
}, {});
}
}
+8 -8
View File
@@ -430,16 +430,16 @@ export default {
fhcApiAxios.interceptors.response.use(
response => {
const errorConfig = get_error_handler(response.config);
if (!errorConfig.success)
if (response.config?.errorHandling == 'off'
|| response.config?.errorHandling === false
|| response.config?.errorHandling == 'fail')
return clean_return_value(response);
const errors = popHandleableErrors(errorConfig, response.data.errors);
for (var type in errors) {
errorConfig.handler[type](errors[type]);
}
// NOTE(chris): loop through errors
if (response.data.errors)
response.data.errors = response.data.errors.filter(
err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, response.config)
);
return clean_return_value(response);
},
-1
View File
@@ -144,7 +144,6 @@ foreach($ps->result as $row)
<ROLLE:stichtagsaktiv><![CDATA['.$stichtagsaktiv.']]></ROLLE:stichtagsaktiv>
<ROLLE:aktiv><![CDATA['.$aktiv.']]></ROLLE:aktiv>
<ROLLE:fgm><![CDATA['.$row->fgm.']]></ROLLE:fgm>
<ROLLE:faktiv><![CDATA['.($row->faktiv?'Ja':'Nein').']]></ROLLE:faktiv>
</RDF:Description>
</RDF:li>
';
+107 -5998
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -92,7 +92,6 @@ 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/71399_dashboard_update_widget_paths.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
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -1,15 +0,0 @@
<?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>';
}
}
+4 -476
View File
@@ -3903,172 +3903,6 @@ $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(
'app' => 'pep',
'category' => 'ui',
@@ -44892,9 +44726,9 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Für den gesamten Studiengang verbindlicher Termin.
Liegt ein Termin in der Vergangenheit, kann nichts mehr hochgeladen werden. Ist es dennoch erforderlich,
'text' => "Für den gesamten Studiengang verbindlicher Termin.
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.",
'description' => '',
'insertvon' => 'system'
@@ -45085,7 +44919,7 @@ array(
array(
'sprache' => 'German',
'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.",
'description' => '',
'insertvon' => 'system'
@@ -45400,46 +45234,6 @@ 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(
'app' => 'core',
'category' => 'abgabetool',
@@ -46901,26 +46695,6 @@ 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
array(
'app' => 'core',
@@ -57174,252 +56948,6 @@ I have been informed that I am under no obligation to consent to the transmissio
)
),
// ### 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
// ### LvPlanStgOrg START ###
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'noStgProvided',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'kein Studiengang hinterlegt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'no degree-program provided',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'headerLvPlanLvVerband',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Lv-Plan Lehrverband',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Course-Plan Teaching Association',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'chooseStg',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Studiengang auswählen...',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Select a degree-program...',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'loadLvPlan',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Lehrverband laden',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Load Course-Plan',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'backToDropdown',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zurück zum Auswahl-Dropdown',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Back to Dropdown',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'error_SemMissing',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Semester wählen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Select a semester',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'error_VerbandMissing',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Verband wählen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Select a verband',
'description' => '',
'insertvon' => 'system'
)
)
),
// ### LvPlanStgOrg END ###
);
+7 -16
View File
@@ -875,6 +875,7 @@ function _getLehrecontainer($sws_proStg_arr)
$sws_proStg_arr = array_filter($sws_proStg_arr, function ($obj) {
return
!in_array($obj->studiengang_kz, BIS_EXCLUDE_STG) &&
$obj->studiengang_kz > 0 &&
$obj->studiengang_kz < 10000;
});
}
@@ -885,17 +886,13 @@ function _getLehrecontainer($sws_proStg_arr)
{
$is_sommersemester = substr($sws_proStg->studiensemester_kurzbz, 0, 2) == 'SS';
$is_wintersemester = substr($sws_proStg->studiensemester_kurzbz, 0, 2) == 'WS';
$is_lehrgang = isset($sws_proStg->lgartcode);
$kennzeichen_name = $is_lehrgang ? 'LehrgangNr' : 'StgKz';
// Lehreobjekt generieren
if (empty($lehre_arr) || !lehre_stg_exists($sws_proStg->studiengang_kz, $lehre_arr))
{
$lehre_obj = new StdClass();
$lehre_obj->{$kennzeichen_name} = $sws_proStg->melde_studiengang_kz;
//~ $lehre_obj->StgKz = setLeadingZero(intval($sws_proStg->studiengang_kz), 4);
$lehre_obj->StgKz = setLeadingZero(intval($sws_proStg->studiengang_kz), 4);
$lehre_obj->SommersemesterSWS = $is_sommersemester ? $sws_proStg->sws : 0.00;
$lehre_obj->WintersemesterSWS = $is_wintersemester ? $sws_proStg->sws : 0.00;
@@ -1023,14 +1020,9 @@ function _generateXML($person_arr)
foreach ($person->lehre_arr as $lehre)
{
$xml .= '<Lehre>';
if (isset($lehre->LehrgangNr))
$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 .= '<StgKz><![CDATA['. $lehre->StgKz. ']]></StgKz>';
$xml .= '<SommersemesterSWS><![CDATA['. $lehre->SommersemesterSWS. ']]></SommersemesterSWS>';
$xml .= '<WintersemesterSWS><![CDATA['. $lehre->WintersemesterSWS. ']]></WintersemesterSWS>';
$xml .= '</Lehre>';
}
@@ -1219,7 +1211,7 @@ function _outputHTML($person_arr)
{
echo '
<tr>
<td>'. (isset($lehre->LehrgangNr) ? $lehre->LehrgangNr : $lehre->StgKz). '</td>
<td>'. $lehre->StgKz. '</td>
<td>'. $lehre->SommersemesterSWS. '</td>
<td>'. $lehre->WintersemesterSWS. '</td>
</tr>';
@@ -1367,8 +1359,7 @@ function lehre_stg_exists($studiengang_kz, $lehre_arr)
{
foreach($lehre_arr as $row)
{
$kennzeichenName = $row->LehrgangNr ?? $row->StgKz;
if(isset($row->{$kennzeichenName}) && $row->{$kennzeichenName} == $melde_studiengang_kz)
if($row->StgKz == $studiengang_kz)
return true;
}
return false;