mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-12 15:04:04 +00:00
Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa026515a5 | ||
|
|
65bf53223c | ||
|
|
1c1e578ee4 | ||
|
|
a300a28047 | ||
|
|
cb09becd09 | ||
|
|
7b3bc68f54 | ||
|
|
80584b64cb | ||
|
|
0e27b26e80 | ||
|
|
794c3b7a5b | ||
|
|
76cd1dda94 | ||
|
|
f5b80cee11 | ||
|
|
523676990b | ||
|
|
a3e02f05f1 | ||
|
|
7d1fa17ab4 | ||
|
|
cfa17943c0 | ||
|
|
c37ef64ba0 | ||
|
|
917721ed3c | ||
|
|
0ccd6b8075 | ||
|
|
febe0d8a65 | ||
|
|
8bd5383f79 | ||
|
|
4983bcd557 | ||
|
|
c3c0518a39 | ||
|
|
e2d26a063c | ||
|
|
8d64c2caaa | ||
|
|
b0996a2d57 | ||
|
|
bbd7d11588 | ||
|
|
e09dd940bc | ||
|
|
f33e244c04 | ||
|
|
c95ff659b8 | ||
|
|
e58b13f776 | ||
|
|
2959a23765 | ||
|
|
62475f7698 | ||
|
|
894ae01a34 | ||
|
|
ae9fa8aac1 | ||
|
|
8e259455eb | ||
|
|
a28cdcee40 | ||
|
|
08cabe6e96 | ||
|
|
892fe4e6ea | ||
|
|
33f4019731 | ||
|
|
2a3af7d924 | ||
|
|
17e74f929b | ||
|
|
e64c998b7a | ||
|
|
8c82c046dc | ||
|
|
f5824c272f | ||
|
|
88715e9dd2 | ||
|
|
b7c0a9aa49 | ||
|
|
41c59aa195 | ||
|
|
63d4a81e80 | ||
|
|
3826ad68f4 | ||
|
|
ad0145b915 | ||
|
|
6324bc50b9 | ||
|
|
3871326880 | ||
|
|
ea3d04d9ae | ||
|
|
e50bc030c9 | ||
|
|
2a138f774a | ||
|
|
bade3753f9 | ||
|
|
054cddfdce | ||
|
|
2a34cb32c8 | ||
|
|
57d8c21f63 | ||
|
|
28b1269324 | ||
|
|
f6efef36ef | ||
|
|
460d2745bc | ||
|
|
5909f55b0e | ||
|
|
41ceb955ff | ||
|
|
3f8ffa05f1 | ||
|
|
ef8090bf2e | ||
|
|
7b7edaf46a | ||
|
|
00b0992fb8 | ||
|
|
74bcfe3c8d | ||
|
|
e2a2d28f90 |
@@ -170,13 +170,6 @@ $config['navigation_header'] = array(
|
||||
'expand' => true,
|
||||
'sort' => 51,
|
||||
'requiredPermissions' => 'vertrag/mitarbeiter:r'
|
||||
),
|
||||
'studierendenverwaltung' => array(
|
||||
'link' => site_url('studentenverwaltung'),
|
||||
'description' => 'Studierendenverwaltung',
|
||||
'expand' => true,
|
||||
'sort' => 52,
|
||||
'requiredPermissions' => ['admin:r', 'assistenz:r']
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -385,8 +378,21 @@ $config['navigation_menu']['apps'] = [
|
||||
],
|
||||
'lav' => [
|
||||
'link' => site_url('lehre/lehrauftrag/Lehrauftrag/Dashboard'),
|
||||
'description' => 'Lehrauftragsverwaltung',
|
||||
'description' => 'Lehraufträge',
|
||||
#'icon' => 'person-chalkboard',
|
||||
'requiredPermissions' => array('lehre/lehrauftrag_bestellen:r', 'lehre/lehrauftrag_erteilen:r')
|
||||
]
|
||||
];
|
||||
|
||||
$config['navigation_menu']['dashboard/*'] = [
|
||||
'dashboards' => [
|
||||
'link' => site_url('dashboard/Admin'),
|
||||
'description' => 'Dashboard',
|
||||
'requiredPermissions' => 'dashboard/admin:rw'
|
||||
],
|
||||
'widgets' => [
|
||||
'link' => site_url('dashboard/Admin/widgets'),
|
||||
'description' => 'Widgets',
|
||||
'requiredPermissions' => 'dashboard/admin:rw'
|
||||
]
|
||||
];
|
||||
|
||||
@@ -129,19 +129,13 @@ $route['api/frontend/v1/treemenudata/stv/.*'] = 'api/frontend/v1/stv/students/in
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/semester/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/semester/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3/$4';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3/$4';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStgOrgform/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3';
|
||||
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2/$3';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2/$3';
|
||||
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/le/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByLe/$1/$2';
|
||||
$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/lv/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByLv/$1/$2';
|
||||
|
||||
|
||||
// load routes from extensions, also look for environment-specific configs
|
||||
$subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions'];
|
||||
|
||||
@@ -26,8 +26,7 @@ $config['root'] = [
|
||||
'verband' => [
|
||||
'group'
|
||||
]
|
||||
]
|
||||
],
|
||||
'orgform'
|
||||
]
|
||||
];
|
||||
|
||||
$config['root']['stg']['orgform'] = $config['root']['stg'];
|
||||
|
||||
@@ -1916,7 +1916,7 @@ class Abgabe extends FHCAPI_Controller
|
||||
|
||||
if (!$mailres)
|
||||
{
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general');
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachterv2'), 'general');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,10 +28,7 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
{
|
||||
parent::__construct([
|
||||
'getByEmp' => ['admin:r', 'assistenz:r'],
|
||||
'getByLe' => ['admin:r', 'assistenz:r'],
|
||||
'getByLv' => ['admin:r', 'assistenz:r'],
|
||||
'getByStg' => ['admin:r', 'assistenz:r'],
|
||||
'getByStgOrgform' => ['admin:r', 'assistenz:r'],
|
||||
'loadByLV' => ['admin:r', 'assistenz:r'],
|
||||
]);
|
||||
|
||||
@@ -80,117 +77,41 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
|
||||
$this->terminateWithSuccess($tree);
|
||||
}
|
||||
|
||||
public function getByLe($studiensemester_kurzbz = null, $lehreinheit_id = null)
|
||||
{
|
||||
if (is_null($lehreinheit_id))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
|
||||
|
||||
$lehreinheiten = $this->_ci->LehreinheitModel->getByLeStudiensemester($lehreinheit_id, $studiensemester_kurzbz);
|
||||
$lehreinheiten_data = $this->getDataOrTerminateWithError($lehreinheiten);
|
||||
|
||||
$this->terminateWithSuccess($lehreinheiten_data);
|
||||
}
|
||||
|
||||
public function getByLv($studiensemester_kurzbz = null, $lehrveranstaltung_id = null)
|
||||
{
|
||||
if (is_null($lehrveranstaltung_id))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
|
||||
$tree = [];
|
||||
|
||||
$this->_ci->LehrveranstaltungModel->addSelect('studiengang_kz');
|
||||
$lv = $this->_ci->LehrveranstaltungModel->load($lehrveranstaltung_id);
|
||||
$lv = $this->getDataOrTerminateWithError($lv);
|
||||
|
||||
if (empty($lv))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$lehrveranstaltungen_data = array();
|
||||
foreach ($this->_loadLvRows(current($lv)->studiengang_kz, $studiensemester_kurzbz) as $row)
|
||||
{
|
||||
if ((string)$row->lehrveranstaltung_id === (string)$lehrveranstaltung_id)
|
||||
{
|
||||
$lehrveranstaltungen_data[] = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($lehrveranstaltungen_data))
|
||||
{
|
||||
|
||||
$this->_ci->LehrveranstaltungModel->addSelect('
|
||||
tbl_lehrveranstaltung.*,
|
||||
tbl_lehrveranstaltung.kurzbz as lv_kurzbz,
|
||||
lehrveranstaltung_id,
|
||||
lehrtyp_kurzbz,
|
||||
studiengang_kz as lv_studiengang_kz,
|
||||
UPPER(CONCAT(tbl_studiengang.typ, tbl_studiengang.kurzbz)) as studiengang,
|
||||
semester as semester,
|
||||
tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
|
||||
ects as lv_ects,
|
||||
lehreverzeichnis as lv_lehreverzeichnis,
|
||||
planfaktor as lv_planfaktor,
|
||||
planlektoren as lv_planlektoren,
|
||||
planpersonalkosten as lv_planpersonalkosten,
|
||||
plankostenprolektor as lv_plankostenprolektor,
|
||||
tbl_lehrveranstaltung.orgform_kurzbz as orgform_kurzbz,
|
||||
tbl_lehrveranstaltung.sprache as sprache,
|
||||
lehrform_kurzbz as lv_lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.bezeichnung_english as lv_bezeichnung_english,
|
||||
semesterstunden as lv_semesterstunden,
|
||||
');
|
||||
$this->_ci->LehrveranstaltungModel->addJoin('public.tbl_studiengang', 'studiengang_kz');
|
||||
$lehrveranstaltungen = $this->_ci->LehrveranstaltungModel->load($lehrveranstaltung_id);
|
||||
$lehrveranstaltungen_data = $this->getDataOrTerminateWithError($lehrveranstaltungen);
|
||||
}
|
||||
|
||||
foreach ($lehrveranstaltungen_data as $lehrveranstaltung)
|
||||
{
|
||||
$lehreinheiten = $this->_ci->LehreinheitModel->getByLvidStudiensemester($lehrveranstaltung->lehrveranstaltung_id, $studiensemester_kurzbz);
|
||||
$lehreinheiten_data = $this->getDataOrTerminateWithError($lehreinheiten);
|
||||
|
||||
if (!isset($lehrveranstaltung->_children))
|
||||
{
|
||||
$lehrveranstaltung->_children = $lehreinheiten_data;
|
||||
}
|
||||
$tree[] = $lehrveranstaltung;
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
$this->assignUniqueIndex($tree, $counter);
|
||||
$this->terminateWithSuccess($tree);
|
||||
}
|
||||
|
||||
public function getByStg($studiensemester_kurzbz = null, $stg_kz = null, $semester = null)
|
||||
{
|
||||
$this->_getLvTree($studiensemester_kurzbz, $stg_kz, $semester, null);
|
||||
}
|
||||
|
||||
public function getByStgOrgform($studiensemester_kurzbz = null, $stg_kz = null, $orgform = null, $semester = null)
|
||||
{
|
||||
$orgform_kurzbz = $this->getOrgformKurzbz($orgform);
|
||||
$this->_getLvTree($studiensemester_kurzbz, $stg_kz, $semester, $orgform_kurzbz);
|
||||
}
|
||||
|
||||
private function _getLvTree($studiensemester_kurzbz, $stg_kz, $semester, $orgform_kurzbz)
|
||||
{
|
||||
$studiengang_kz = $this->getStudiengangKz($stg_kz);
|
||||
|
||||
if (is_null($studiengang_kz) || !preg_match("/^(-?[1-9][0-9]*|0)$/", (string)$studiengang_kz))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
|
||||
if (!is_null($semester) && !ctype_digit((string)$semester))
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
$verband = null;
|
||||
if (!is_null($semester) && !is_numeric($semester))
|
||||
{
|
||||
$verband = $this->getOrgformKurzbz($semester);
|
||||
$semester = null;
|
||||
}
|
||||
|
||||
$this->_ci->load->model('organisation/Studienplan_model', 'StudienplanModel');
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
$studienplan_data = $this->_ci->StudienplanModel->getStudienplaeneBySemester($studiengang_kz, $studiensemester_kurzbz, $semester, $verband);
|
||||
|
||||
$lehrveranstaltungen_data = $this->_loadLvRows($studiengang_kz, $studiensemester_kurzbz, $semester, $orgform_kurzbz);
|
||||
$studienplan_ids = array();
|
||||
$only_ids = array();
|
||||
$placeholders = array();
|
||||
|
||||
if (hasData($studienplan_data))
|
||||
{
|
||||
foreach (getData($studienplan_data) as $studienplan) {
|
||||
$placeholders[] = "(?, ?)";
|
||||
$studienplan_ids[] = $studienplan->studienplan_id;
|
||||
$studienplan_ids[] = $studienplan->semester;
|
||||
$only_ids[] = $studienplan->studienplan_id;
|
||||
}
|
||||
}
|
||||
|
||||
$lehrveranstaltungen_data = $this->_ci->LehrveranstaltungModel->getLvsByStudiengang($studienplan_ids, $placeholders, $only_ids, $studiengang_kz, $studiensemester_kurzbz, $semester, $verband);
|
||||
$lehrveranstaltungen_data = hasData($lehrveranstaltungen_data) ? getData($lehrveranstaltungen_data) : array();
|
||||
|
||||
$tree = [];
|
||||
foreach ($lehrveranstaltungen_data as $row)
|
||||
@@ -323,31 +244,6 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
|
||||
$this->LehreinheitModel->getLvsByFachbereich($fachbereich, $studiensemester_kurzbz, $mitarbeiter_uid);
|
||||
}*/
|
||||
|
||||
private function _loadLvRows($studiengang_kz, $studiensemester_kurzbz, $semester = null, $orgform_kurzbz = null)
|
||||
{
|
||||
$this->_ci->load->model('organisation/Studienplan_model', 'StudienplanModel');
|
||||
|
||||
$studienplan_data = $this->_ci->StudienplanModel->getStudienplaeneBySemester($studiengang_kz, $studiensemester_kurzbz, $semester, $orgform_kurzbz);
|
||||
|
||||
$studienplan_ids = array();
|
||||
$only_ids = array();
|
||||
$placeholders = array();
|
||||
|
||||
if (hasData($studienplan_data))
|
||||
{
|
||||
foreach (getData($studienplan_data) as $studienplan) {
|
||||
$placeholders[] = "(?, ?)";
|
||||
$studienplan_ids[] = $studienplan->studienplan_id;
|
||||
$studienplan_ids[] = $studienplan->semester;
|
||||
$only_ids[] = $studienplan->studienplan_id;
|
||||
}
|
||||
}
|
||||
|
||||
$lehrveranstaltungen_data = $this->_ci->LehrveranstaltungModel->getLvsByStudiengang($studienplan_ids, $placeholders, $only_ids, $studiengang_kz, $studiensemester_kurzbz, $semester, $orgform_kurzbz);
|
||||
return hasData($lehrveranstaltungen_data) ? getData($lehrveranstaltungen_data) : array();
|
||||
}
|
||||
|
||||
private function _setAuthUID()
|
||||
{
|
||||
$this->_uid = getAuthUID();
|
||||
@@ -413,8 +309,8 @@ class Lehrveranstaltung extends FHCAPI_Controller
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
if (!$data)
|
||||
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
|
||||
return $orgform_kurzbz;
|
||||
|
||||
return current($data)->orgform_kurzbz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
<?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');
|
||||
|
||||
use \DateTime as DateTime;
|
||||
|
||||
class Report extends FHCAPI_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Object initialization
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'list' => [ 'dashboard/admin:rw', 'basis/statistik:rw' ],
|
||||
'vars' => self::PERM_LOGGED, // additional permission check inside
|
||||
'get' => self::PERM_LOGGED, // additional permission check inside
|
||||
]);
|
||||
|
||||
$this->load->model('organisation/Statistik_model', 'StatistikModel');
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function list()
|
||||
{
|
||||
$this->StatistikModel->addSelect('statistik_kurzbz');
|
||||
$this->StatistikModel->addSelect('bezeichnung');
|
||||
$this->StatistikModel->addSelect('gruppe');
|
||||
|
||||
$result = $this->StatistikModel->loadWhere([
|
||||
'sql IS NOT NULL' => null
|
||||
]);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function vars($statistik_kurzbz)
|
||||
{
|
||||
$result = $this->StatistikModel->load($statistik_kurzbz);
|
||||
$statistik = $this->getDataOrTerminateWithError($result);
|
||||
if (!$statistik)
|
||||
show_404();
|
||||
$statistik = current($statistik);
|
||||
|
||||
// NOTE(chris): Permission check
|
||||
$permissions = [ 'vars' => $statistik->berechtigung_kurzbz . ':r' ];
|
||||
if (!$this->permissionlib->isEntitled($permissions, 'vars')) {
|
||||
$this->_outputAuthError($permissions);
|
||||
exit; // immediately terminate the execution
|
||||
}
|
||||
|
||||
$this->addMeta('berechtigung_kurzbz', $statistik->berechtigung_kurzbz);
|
||||
|
||||
$vars = $this->loadVars($statistik->sql);
|
||||
|
||||
$this->terminateWithSuccess($vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access protected
|
||||
*/
|
||||
protected function loadVars($sql)
|
||||
{
|
||||
$result = [];
|
||||
preg_match_all('/\$\w+/', $sql ?: '', $result);
|
||||
$result = $result[0];
|
||||
|
||||
$vars = array_unique($result); // remove doubles
|
||||
|
||||
$this->load->model('system/Filter_model', 'FilterModel');
|
||||
$vars = array_map([$this, 'varToFilter'], $vars); // prep them
|
||||
|
||||
$vars = array_filter($vars); // remove null values (former: $user)
|
||||
|
||||
return array_values($vars); // reindex
|
||||
}
|
||||
|
||||
/**
|
||||
* @access protected
|
||||
*/
|
||||
protected function varToFilter($value)
|
||||
{
|
||||
if ($value == '$user')
|
||||
return null;
|
||||
|
||||
$value = str_replace('$', '', $value);
|
||||
|
||||
$result = $this->FilterModel->loadWhere([
|
||||
'kurzbz' => $value
|
||||
]);
|
||||
$filter = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$result = [];
|
||||
if (!$filter) {
|
||||
$result = [
|
||||
'kurzbz' => $value,
|
||||
'title' => $value,
|
||||
'type' => 'text',
|
||||
];
|
||||
} else {
|
||||
$filter = current($filter);
|
||||
$result = [
|
||||
'kurzbz' => $filter->kurzbz,
|
||||
'title' => $filter->bezeichnung ?: $filter->kurzbz,
|
||||
'type' => $filter->type,
|
||||
];
|
||||
switch ($filter->type) {
|
||||
case 'select':
|
||||
$sql = str_replace('$user', $this->FilterModel->escape(getAuthUID()), $filter->sql);
|
||||
$query = $this->FilterModel->execReadOnlyQuery($sql);
|
||||
$options = $this->getDataOrTerminateWithError($query);
|
||||
$showValue = $filter->showvalue;
|
||||
$result['options'] = array_map(function ($option) use ($showValue) {
|
||||
$label = '';
|
||||
if ($showValue)
|
||||
$label = '(' + $option->value + ') - ';
|
||||
if (property_exists($option, 'name'))
|
||||
$label .= $option->name;
|
||||
else
|
||||
$label .= ((array)$option)[array_find(array_keys((array)$option), function ($key) {
|
||||
return $key != 'value';
|
||||
})];
|
||||
return [
|
||||
'value' => $option->value,
|
||||
'label' => $label,
|
||||
];
|
||||
}, $options);
|
||||
|
||||
if (preg_match('/(^|\s)multiple($|\s)/', $filter->htmlattr ?: '')) {
|
||||
$result['multiple'] = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/(^|\s)placeholder="([^"]*)"($|\s)/', $filter->htmlattr ?: '', $matches)) {
|
||||
$result['placeholder'] = $matches[2];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function get($statistik_kurzbz)
|
||||
{
|
||||
$result = $this->StatistikModel->load($statistik_kurzbz);
|
||||
$statistik = $this->getDataOrTerminateWithError($result);
|
||||
if (!$statistik)
|
||||
show_404();
|
||||
$statistik = current($statistik);
|
||||
|
||||
// NOTE(chris): Permission check
|
||||
$permissions = [ 'get' => $statistik->berechtigung_kurzbz . ':r' ];
|
||||
if (!$this->permissionlib->isEntitled($permissions, 'get')) {
|
||||
$this->_outputAuthError($permissions);
|
||||
exit; // immediately terminate the execution
|
||||
}
|
||||
|
||||
$vars = $this->loadVars($statistik->sql);
|
||||
$vars_values = [];
|
||||
|
||||
if (count($vars)) {
|
||||
$this->load->library('form_validation');
|
||||
foreach ($vars as $var) {
|
||||
$key = $var['kurzbz'];
|
||||
$label = $var['title'];
|
||||
$checks = 'required';
|
||||
|
||||
if (isset($var['multiple']) && $var['multiple']) {
|
||||
$key .= '[]';
|
||||
}
|
||||
|
||||
if ($var['type'] == 'select') {
|
||||
$options = array_map(function ($opt) {
|
||||
return $opt['value'];
|
||||
}, $var['options']);
|
||||
$checks .= '|strval|in_list[' . implode(',', $options) . ']';
|
||||
}
|
||||
|
||||
if ($var['type'] == 'datepicker')
|
||||
$checks .= '|is_valid_date';
|
||||
|
||||
$this->form_validation->set_rules($key, $label, $checks);
|
||||
|
||||
$vars_values[$var['kurzbz']] = $this->input->post($var['kurzbz']);
|
||||
|
||||
if ($var['type'] == 'datepicker') {
|
||||
try {
|
||||
$dt = new DateTime($vars_values[$var['kurzbz']]);
|
||||
$vars_values[$var['kurzbz']] = $dt->format('c');
|
||||
} catch(Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->form_validation->run())
|
||||
$this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
}
|
||||
|
||||
$db = $this->StatistikModel;
|
||||
$sql = preg_replace_callback('/\$\w+/', function ($match) use ($db, $vars_values) {
|
||||
if ($match[0] == '$user')
|
||||
return $db->escape(getAuthUID());
|
||||
|
||||
$key = substr($match[0], 1);
|
||||
if (isset($vars_values[$key])) {
|
||||
$value = $vars_values[$key];
|
||||
if (is_array($value)) {
|
||||
$value = implode(', ', array_map(function ($v) use ($db) {
|
||||
return $db->escape($v);
|
||||
}, $value));
|
||||
} else {
|
||||
$value = $db->escape($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $match[0];
|
||||
}, $statistik->sql);
|
||||
|
||||
$result = $this->StatistikModel->execReadOnlyQuery($sql);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
use \DirectoryIterator as DirectoryIterator;
|
||||
|
||||
/**
|
||||
* This controller operates between (interface) the JS (GUI) and the back-end
|
||||
* Provides data to the ajax get calls about the users dashboard
|
||||
@@ -31,7 +33,13 @@ class Widget extends FHCAPI_Controller
|
||||
'get' => ['dashboard/benutzer:r', 'dashboard/admin:r'],
|
||||
'list' => 'dashboard/admin:r',
|
||||
'listAllowed' => ['dashboard/benutzer:rw', 'dashboard/admin:r'],
|
||||
'setAllowed' => 'dashboard/admin:rw'
|
||||
'listAll' => 'dashboard/admin:r',
|
||||
'listAllOriginal' => 'dashboard/admin:rw',
|
||||
'setAllowed' => 'dashboard/admin:rw',
|
||||
'create' => 'dashboard/admin:rw',
|
||||
'update' => 'dashboard/admin:rw',
|
||||
'remove' => 'dashboard/admin:rw',
|
||||
'generators' => 'dashboard/admin:rw',
|
||||
]);
|
||||
|
||||
// Libraries
|
||||
@@ -110,6 +118,38 @@ class Widget extends FHCAPI_Controller
|
||||
$this->terminateWithSuccess($widgets);
|
||||
}
|
||||
|
||||
public function listAll()
|
||||
{
|
||||
$result = $this->WidgetModel->load();
|
||||
|
||||
$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 listAllOriginal()
|
||||
{
|
||||
$result = $this->WidgetModel->load();
|
||||
|
||||
$widgets = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$widgets = array_map(function ($widget) {
|
||||
$widget->arguments = json_decode($widget->arguments);
|
||||
$widget->setup = json_decode($widget->setup);
|
||||
return $widget;
|
||||
}, $widgets);
|
||||
|
||||
$this->terminateWithSuccess($widgets);
|
||||
}
|
||||
|
||||
public function setAllowed()
|
||||
{
|
||||
$this->load->library('form_validation');
|
||||
@@ -137,4 +177,185 @@ class Widget extends FHCAPI_Controller
|
||||
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->loadPhrases([ 'ui', 'dashboard' ]);
|
||||
$this->load->library('form_validation');
|
||||
|
||||
$this->form_validation->set_rules(
|
||||
'widget_kurzbz',
|
||||
$this->p->t('dashboard', 'widget_kurzbz'),
|
||||
'required|is_not_in_db[dashboard/Widget_model:widget_kurzbz]',
|
||||
[
|
||||
'is_not_in_db' => $this->p->t('ui', 'error_entryExisting')
|
||||
]
|
||||
);
|
||||
$this->form_validation->set_rules(
|
||||
'setup[generator]',
|
||||
$this->p->t('dashboard', 'generator'),
|
||||
'required'
|
||||
);
|
||||
|
||||
if (!$this->form_validation->run())
|
||||
$this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
$setup = $this->input->post('setup');
|
||||
|
||||
$file = explode(':', $setup['generator']);
|
||||
if (count($file) == 1) {
|
||||
$setup['file'] = 'public/js/components/DashboardWidget/' . $file[0] . '.js';
|
||||
} else {
|
||||
$setup['file'] = 'public/extensions/' . $file[0] . '/js/components/DashboardWidget/' . $file[1] . '.js';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'widget_kurzbz' => $this->input->post('widget_kurzbz'),
|
||||
'setup' => json_encode($setup),
|
||||
'arguments' => json_encode([]),
|
||||
];
|
||||
$result = $this->WidgetModel->insert($data);
|
||||
|
||||
$data['widget_id'] = $this->getDataOrTerminateWithError($result);
|
||||
$data['setup'] = $setup;
|
||||
$data['arguments'] = [];
|
||||
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$this->loadPhrases([ 'dashboard' ]);
|
||||
$this->load->library('form_validation');
|
||||
|
||||
$this->form_validation->set_rules(
|
||||
'widget_id',
|
||||
$this->p->t('dashboard', 'widget_id'),
|
||||
'required'
|
||||
);
|
||||
|
||||
if (!$this->form_validation->run())
|
||||
$this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
$widget_id = $this->input->post('widget_id');
|
||||
$data = [];
|
||||
foreach ([
|
||||
'widget_kurzbz',
|
||||
'beschreibung',
|
||||
'setup',
|
||||
'arguments',
|
||||
'berechtigung_kurzbz',
|
||||
] as $post) {
|
||||
$value = $this->input->post($post);
|
||||
if ($value) {
|
||||
if ($post == 'setup' || $post == 'arguments')
|
||||
$value = json_encode($value);
|
||||
$data[$post] = $value;
|
||||
}
|
||||
}
|
||||
if (!$data)
|
||||
$this->terminateWithSuccess();
|
||||
|
||||
$result = $this->WidgetModel->update($widget_id, $data);
|
||||
|
||||
$this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess();
|
||||
}
|
||||
|
||||
public function remove()
|
||||
{
|
||||
$this->loadPhrases([ 'dashboard' ]);
|
||||
$this->load->library('form_validation');
|
||||
|
||||
$this->form_validation->set_rules(
|
||||
'widget_id',
|
||||
$this->p->t('dashboard', 'widget_id'),
|
||||
'required'
|
||||
);
|
||||
|
||||
if (!$this->form_validation->run())
|
||||
$this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
$widget_id = $this->input->post('widget_id');
|
||||
|
||||
$result = $this->WidgetModel->load($widget_id);
|
||||
$widget = $this->getDataOrTerminateWithError($result);
|
||||
$widget = current($widget);
|
||||
if (!$widget)
|
||||
show_404();
|
||||
|
||||
$setup = json_decode($widget->setup);
|
||||
|
||||
if (!property_exists($setup, 'generator'))
|
||||
$this->terminateWithError($this->p->t('dashboard', 'widget_error_delete_native'), null, REST_Controller::HTTP_METHOD_NOT_ALLOWED);
|
||||
|
||||
$result = $this->WidgetModel->delete($widget_id);
|
||||
|
||||
$this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess();
|
||||
}
|
||||
|
||||
public function generators()
|
||||
{
|
||||
$result = $this->getGeneratorsRecursive();
|
||||
|
||||
$this->load->library('ExtensionsLib');
|
||||
$extensions = $this->extensionslib->getInstalledExtensions();
|
||||
if (hasData($extensions)) {
|
||||
foreach (getData($extensions) as $extension) {
|
||||
$result = array_merge($result, $this->getGeneratorsRecursive($extension->name));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->terminateWithSuccess($result);
|
||||
}
|
||||
|
||||
protected function getGeneratorsRecursive($extension = '', $path = '')
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$dashboard_widget_path = 'public/js/components/DashboardWidget/';
|
||||
|
||||
$name = '';
|
||||
$file_path = $dashboard_widget_path . $path;
|
||||
$generator_path = $dashboard_widget_path . 'Generators/' . $path;
|
||||
|
||||
if ($extension) {
|
||||
$name = $extension . ':';
|
||||
$file_path = APPPATH . 'extensions/' . $extension . '/' . $file_path;
|
||||
$generator_path = APPPATH . 'extensions/' . $extension . '/' . $generator_path;
|
||||
} else {
|
||||
$file_path = FHCPATH . $file_path;
|
||||
$generator_path = FHCPATH . $generator_path;
|
||||
}
|
||||
|
||||
$name .= $path;
|
||||
|
||||
$generator_path = str_replace('/', DIRECTORY_SEPARATOR, $generator_path);
|
||||
|
||||
if (is_dir($generator_path)) {
|
||||
$dir = new DirectoryIterator($generator_path);
|
||||
|
||||
foreach ($dir as $file) {
|
||||
if ($file->isDot())
|
||||
continue;
|
||||
$filename = $file->getFilename();
|
||||
if ($file->isDir()) {
|
||||
if (is_dir(str_replace('/', DIRECTORY_SEPARATOR, $file_path . $filename))) {
|
||||
$res = $this->getGeneratorsRecursive($extension, $path . $filename . '/');
|
||||
$result = array_merge($result, $res);
|
||||
}
|
||||
} else {
|
||||
if (file_exists(str_replace('/', DIRECTORY_SEPARATOR, $file_path . $filename))) {
|
||||
if ($file->getExtension() == 'js')
|
||||
$result[] = $name . $file->getBasename('.js');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ class StudiengangEP extends FHCAPI_Controller
|
||||
{
|
||||
parent::__construct(
|
||||
array(
|
||||
'getStudiengangByKz' => self::PERM_LOGGED
|
||||
'getStudiengangByKz' => self::PERM_LOGGED,
|
||||
'getAllowed' => self::PERM_LOGGED,
|
||||
)
|
||||
);
|
||||
// Load model StudiengangModel
|
||||
@@ -54,4 +55,29 @@ class StudiengangEP extends FHCAPI_Controller
|
||||
}
|
||||
$this->terminateWithSuccess($stg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all STGs they user has the given permissions for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getAllowed()
|
||||
{
|
||||
$this->loadPhrases([ 'global' ]);
|
||||
$this->load->library('form_validation');
|
||||
|
||||
$this->form_validation->set_rules('permissions[]', $this->p->t('global', 'permissions'), 'required');
|
||||
|
||||
if (!$this->form_validation->run())
|
||||
$this->terminateWithValidationErrors($this->form_validation->error_array());
|
||||
|
||||
$permissions = $this->input->post('permissions');
|
||||
|
||||
$permittedStgs = [];
|
||||
|
||||
foreach ($permissions as $permission)
|
||||
$permittedStgs = array_merge($permittedStgs, $this->permissionlib->getSTG_isEntitledFor($permission) ?: []);
|
||||
|
||||
$this->terminateWithSuccess($permittedStgs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ class Studienjahr extends FHCAPI_Controller
|
||||
parent::__construct(
|
||||
array(
|
||||
'getAll' => self::PERM_LOGGED,
|
||||
'getNext' => self::PERM_LOGGED
|
||||
'getNext' => self::PERM_LOGGED,
|
||||
'getAktNext' => self::PERM_LOGGED
|
||||
)
|
||||
);
|
||||
// Load model StudiensemesterModel
|
||||
@@ -77,4 +78,32 @@ class Studienjahr extends FHCAPI_Controller
|
||||
|
||||
$this->terminateWithSuccess(current(getData($result)));
|
||||
}
|
||||
|
||||
public function getAktNext()
|
||||
{
|
||||
$this->StudienjahrModel->addJoin('public.tbl_studiensemester', 'studienjahr_kurzbz');
|
||||
$this->StudienjahrModel->addOrder('ende');
|
||||
$this->StudienjahrModel->addLimit(1);
|
||||
|
||||
$result = $this->StudienjahrModel->loadWhere([
|
||||
'start <=' => 'NOW()',
|
||||
'ende >=' => 'NOW()'
|
||||
]);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
if (!$data) {
|
||||
$this->StudienjahrModel->addJoin('public.tbl_studiensemester', 'studienjahr_kurzbz');
|
||||
$this->StudienjahrModel->addOrder('ende');
|
||||
$this->StudienjahrModel->addLimit(1);
|
||||
|
||||
$result = $this->StudienjahrModel->loadWhere([
|
||||
'ende >=' => 'NOW()'
|
||||
]);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess(current($data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ class Studiensemester extends FHCAPI_Controller
|
||||
'getStudienjahrByStudiensemester' => self::PERM_LOGGED,
|
||||
'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED,
|
||||
'current' => self::PERM_LOGGED,
|
||||
'set' => self::PERM_LOGGED,
|
||||
'getContainingOrNearestByDateRange' => self::PERM_LOGGED,
|
||||
'set' => self::PERM_LOGGED
|
||||
)
|
||||
);
|
||||
// Load model StudiensemesterModel
|
||||
@@ -204,15 +203,4 @@ class Studiensemester extends FHCAPI_Controller
|
||||
|
||||
$this->terminateWithSuccess(true);
|
||||
}
|
||||
|
||||
public function getContainingOrNearestByDateRange()
|
||||
{
|
||||
$from = $this->input->get("from");
|
||||
$to = $this->input->get("to");
|
||||
|
||||
$result = $this->StudiensemesterModel->getContainingOrNearestByDateRange($from, $to);
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,19 +574,6 @@ class Kontakt extends FHCAPI_Controller
|
||||
$person_id = $this->input->post('person_id');
|
||||
$standort_id = $this->input->post('standort_id');
|
||||
|
||||
//check person_id: allow editing only of persons who are no mitarbeiter
|
||||
if($person_id)
|
||||
{
|
||||
$this->load->model('person/Benutzer_model', 'BenutzerModel');
|
||||
$this->BenutzerModel->addJoin('public.tbl_mitarbeiter ma', 'ON (ma.mitarbeiter_uid = public.tbl_benutzer.uid)');
|
||||
|
||||
$result = $this->BenutzerModel->loadWhere([
|
||||
'person_id' => $person_id
|
||||
]);
|
||||
if(hasData($result))
|
||||
return $this->terminateWithError($this->p->t('ui', 'editingMitarbeiterPVonly'), self::ERROR_TYPE_GENERAL);
|
||||
}
|
||||
|
||||
$result = $this->KontaktModel->update(
|
||||
[
|
||||
'kontakt_id' => $kontakt_id
|
||||
|
||||
@@ -31,6 +31,7 @@ class Admin extends Auth_Controller
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'dashboard/admin:rw',
|
||||
'widgets' => 'dashboard/admin:rw',
|
||||
'preview' => 'dashboard/admin:r',
|
||||
)
|
||||
);
|
||||
@@ -43,6 +44,11 @@ class Admin extends Auth_Controller
|
||||
$this->load->view('dashboard/admin.php', []);
|
||||
}
|
||||
|
||||
public function widgets()
|
||||
{
|
||||
$this->load->view('dashboard/widgets.php');
|
||||
}
|
||||
|
||||
public function preview($dashboard_kurzbz = 'CIS')
|
||||
{
|
||||
$this->load->view('dashboard/preview.php', [
|
||||
|
||||
@@ -529,6 +529,17 @@ function is_in_db($key, $model = '')
|
||||
return (isSuccess($result) && hasData($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* check if an entry does not exist in the database
|
||||
*/
|
||||
function is_not_in_db($key, $model = '')
|
||||
{
|
||||
if (!$model)
|
||||
return false;
|
||||
|
||||
return !is_in_db($key, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* is building an array for Dropdown Entry in Print Dropdown
|
||||
* @param $id id for the Document to add to the Document Array
|
||||
|
||||
@@ -33,7 +33,7 @@ class SearchBarLib
|
||||
const ERROR_NOT_AUTH = 'ERR005';
|
||||
|
||||
// List of allowed types of search
|
||||
const ALLOWED_TYPES = ['mitarbeiter', 'mitarbeiter_ohne_zuordnung', 'organisationunit', 'raum', 'person', 'student','studentStv', 'prestudent', 'document', 'cms', 'lehreinheit', 'lehrveranstaltung'];
|
||||
const ALLOWED_TYPES = ['mitarbeiter', 'mitarbeiter_ohne_zuordnung', 'organisationunit', 'raum', 'person', 'student','studentStv', 'prestudent', 'document', 'cms'];
|
||||
|
||||
const PHOTO_IMG_URL = '/cis/public/bild.php?src=person&person_id=';
|
||||
|
||||
@@ -180,14 +180,10 @@ class SearchBarLib
|
||||
protected function buildSearchClause(DB_Model $dbModel, array $columns, $searchstr)
|
||||
{
|
||||
$searchstr = preg_replace('/[[:punct:]]/', ' ', $searchstr);
|
||||
$words = preg_split('/\s+/', trim($searchstr), -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (empty($words)) return array();
|
||||
|
||||
$document = implode(' || \' \' || ', $columns);
|
||||
$query = '\'' . implode(':* & ', $words) . ':*\'';
|
||||
$reversequery = '\'*:' . implode(' & *:', $words) . '\'';
|
||||
$nospacequery = '\'' . implode('', $words) . ':*\'';
|
||||
$query = '\'' . implode(':* & ', explode(' ', trim($searchstr))) . ':*\'';
|
||||
$reversequery = '\'*:' . implode(' & *:', explode(' ', trim($searchstr))) . '\'';
|
||||
$nospacequery = '\'' . implode('', explode(' ', trim($searchstr))) . ':*\'';
|
||||
|
||||
$searchclause = <<<EOSC
|
||||
to_tsvector(lower(regexp_replace({$document}, '[[:punct:]]', ' ', 'g'))) @@ to_tsquery(lower({$query}))
|
||||
@@ -600,98 +596,5 @@ EOSC;
|
||||
// Otherwise return an empty array
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
private function _lehreinheit($searchstr, $type)
|
||||
{
|
||||
$dbModel = new DB_Model();
|
||||
|
||||
$idwhere = '';
|
||||
if (ctype_digit(trim($searchstr)))
|
||||
{
|
||||
$id = (int)trim($searchstr);
|
||||
$idwhere = ' OR le.lehreinheit_id = ' . $id;
|
||||
}
|
||||
|
||||
$lehreinheit = $dbModel->execReadOnlyQuery('
|
||||
SELECT
|
||||
\'teachingunit\' AS renderer,
|
||||
\''.$type.'\' AS type,
|
||||
le.lehreinheit_id AS id,
|
||||
le.studiensemester_kurzbz,
|
||||
lv.bezeichnung,
|
||||
\'lv_table_icon icon-\' AS foto,
|
||||
UPPER(stg.typ || stg.kurzbz) AS studiengang,
|
||||
le.lehrform_kurzbz,
|
||||
lv.semester
|
||||
FROM lehre.tbl_lehreinheit le
|
||||
JOIN lehre.tbl_lehrveranstaltung lv ON (lv.lehrveranstaltung_id = le.lehrveranstaltung_id)
|
||||
JOIN public.tbl_studiengang stg ON (stg.studiengang_kz = lv.studiengang_kz)
|
||||
JOIN public.tbl_studiensemester sem ON (sem.studiensemester_kurzbz = le.studiensemester_kurzbz)
|
||||
WHERE (' .
|
||||
$this->buildSearchClause(
|
||||
$dbModel,
|
||||
array(
|
||||
'(stg.typ || stg.kurzbz)',
|
||||
'lv.semester',
|
||||
'lv.bezeichnung'
|
||||
),
|
||||
$searchstr
|
||||
) . ')' . $idwhere . '
|
||||
');
|
||||
|
||||
// If something has been found then return it
|
||||
if (hasData($lehreinheit)) return getData($lehreinheit);
|
||||
|
||||
// Otherwise return an empty array
|
||||
return array();
|
||||
}
|
||||
|
||||
private function _lehrveranstaltung($searchstr, $type)
|
||||
{
|
||||
$dbModel = new DB_Model();
|
||||
|
||||
$idwhere = '';
|
||||
|
||||
if (ctype_digit(trim($searchstr)))
|
||||
{
|
||||
$id = (int)trim($searchstr);
|
||||
$idwhere = ' OR tbl_lehrveranstaltung.lehrveranstaltung_id = ' . $id;
|
||||
}
|
||||
|
||||
$lehreinheit = $dbModel->execReadOnlyQuery('
|
||||
|
||||
SELECT
|
||||
\'teachingunit\' AS renderer,
|
||||
\''.$type.'\' AS type,
|
||||
lehrveranstaltung_id as id,
|
||||
tbl_lehrveranstaltung.bezeichnung,
|
||||
\'lv_table_icon icon-lv\' as foto,
|
||||
UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as studiengang,
|
||||
semester,
|
||||
lehrform_kurzbz,
|
||||
tbl_organisationseinheit.bezeichnung as oe_bezeichnung
|
||||
FROM lehre.tbl_lehrveranstaltung
|
||||
JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
JOIN public.tbl_organisationseinheit ON tbl_lehrveranstaltung.oe_kurzbz = tbl_organisationseinheit.oe_kurzbz
|
||||
WHERE (' .
|
||||
$this->buildSearchClause(
|
||||
$dbModel,
|
||||
array(
|
||||
'(tbl_studiengang.typ || tbl_studiengang.kurzbz)',
|
||||
'semester',
|
||||
'tbl_lehrveranstaltung.bezeichnung'
|
||||
),
|
||||
$searchstr
|
||||
) . ')' . $idwhere . '
|
||||
');
|
||||
|
||||
// If something has been found then return it
|
||||
if (hasData($lehreinheit)) return getData($lehreinheit);
|
||||
|
||||
// Otherwise return an empty array
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,8 @@ class LvVwLib
|
||||
if (!$stg->mischform)
|
||||
return [];
|
||||
|
||||
$this->_ci->StudiengangModel->db->select('TRUE AS leaf', false);
|
||||
|
||||
return $this->_ci->stglib->orgform($path_template, $stg->studiengang_kz);
|
||||
}
|
||||
|
||||
|
||||
@@ -340,20 +340,6 @@ EOSQL;
|
||||
}
|
||||
|
||||
|
||||
public function getByLeStudiensemester($lehreinheit_id, $studiensemester_kurzbz)
|
||||
{
|
||||
$qry = "WITH lehreinheiten AS (
|
||||
SELECT *
|
||||
FROM lehre.tbl_lehreinheit
|
||||
WHERE lehreinheit_id = ?
|
||||
AND studiensemester_kurzbz = ?
|
||||
)," . $this->_getLeCTE();
|
||||
|
||||
$params = array($lehreinheit_id, $studiensemester_kurzbz);
|
||||
|
||||
return $this->execReadOnlyQuery($qry, $params);
|
||||
|
||||
}
|
||||
public function getByLvidStudiensemester($lv_id, $studiensemester_kurzbz, $mitarbeiter_uid = null, $fachbereich_kurzbz = null)
|
||||
{
|
||||
$qry = "WITH lehreinheiten AS (
|
||||
@@ -361,7 +347,35 @@ EOSQL;
|
||||
FROM lehre.tbl_lehreinheit
|
||||
WHERE lehrveranstaltung_id = ?
|
||||
AND studiensemester_kurzbz = ?
|
||||
)," . $this->_getLeCTE();
|
||||
),
|
||||
". $this->_getGruppenCTE() . ",
|
||||
". $this->_getLektorenCTE() . ",
|
||||
". $this->_getFachbereichCTE() . ",
|
||||
". $this->_getTagsCTE() . "
|
||||
|
||||
SELECT lehreinheiten.*,
|
||||
lehreinheiten.lehrform_kurzbz as lv_lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.kurzbz as lv_kurzbz,
|
||||
tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
|
||||
COALESCE(tag_data_agg.tags, '[]'::json) AS tags,
|
||||
gruppen.gruppen,
|
||||
mitarbeiter.lektoren,
|
||||
mitarbeiter.le_planstunden,
|
||||
mitarbeiter.vorname,
|
||||
mitarbeiter.nachname,
|
||||
mitarbeiter.semesterstunden,
|
||||
fachbereich.bezeichnung as fachbereich,
|
||||
UPPER(CONCAT(tbl_studiengang.typ,tbl_studiengang.kurzbz)) as studiengang,
|
||||
semester
|
||||
FROM lehreinheiten
|
||||
LEFT JOIN lehre.tbl_lehrveranstaltung ON tbl_lehrveranstaltung.lehrveranstaltung_id = lehreinheiten.lehrfach_id
|
||||
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
LEFT JOIN tag_data_agg ON tag_data_agg.lehreinheit_id = lehreinheiten.lehreinheit_id
|
||||
LEFT JOIN mitarbeiter ON lehreinheiten.lehreinheit_id = mitarbeiter.lehreinheit_id
|
||||
LEFT JOIN fachbereich ON lehreinheiten.lehreinheit_id = fachbereich.lehreinheit_id
|
||||
LEFT JOIN gruppen ON lehreinheiten.lehreinheit_id = gruppen.lehreinheit_id
|
||||
WHERE true
|
||||
";
|
||||
|
||||
$params = array($lv_id, $studiensemester_kurzbz);
|
||||
|
||||
@@ -381,83 +395,9 @@ EOSQL;
|
||||
return $this->execReadOnlyQuery($qry, $params);
|
||||
}
|
||||
|
||||
public function getOffeneStunden($lehreinheit_ids)
|
||||
private function getLVTmp($stg_kz = null)
|
||||
{
|
||||
if (!is_array($lehreinheit_ids))
|
||||
$lehreinheit_ids = [$lehreinheit_ids];
|
||||
|
||||
if (empty($lehreinheit_ids))
|
||||
return success([]);
|
||||
|
||||
$db = new DB_Model();
|
||||
$placeholders = implode(',', array_fill(0, count($lehreinheit_ids), '?'));
|
||||
|
||||
$qry = "WITH eventdauer AS (
|
||||
SELECT
|
||||
tbl_kalender_lehreinheit.lehreinheit_id,
|
||||
tbl_kalender.von,
|
||||
tbl_kalender.bis,
|
||||
tbl_kalender.kalender_id
|
||||
FROM lehre.tbl_lehreinheit
|
||||
JOIN lehre.tbl_kalender_lehreinheit USING(lehreinheit_id)
|
||||
JOIN lehre.tbl_kalender USING(kalender_id)
|
||||
WHERE tbl_lehreinheit.lehreinheit_id IN ($placeholders)
|
||||
AND NOT EXISTS (SELECT 1 FROM lehre.tbl_kalender nachfolger
|
||||
WHERE nachfolger.vorgaenger_kalender_id = tbl_kalender.kalender_id)
|
||||
AND tbl_kalender.status_kurzbz NOT IN ('deleted', 'todelete', 'archived')
|
||||
GROUP BY tbl_kalender_lehreinheit.lehreinheit_id, tbl_kalender.von, tbl_kalender.bis, tbl_kalender.kalender_id
|
||||
),
|
||||
raster AS (
|
||||
SELECT
|
||||
eventdauer.lehreinheit_id,
|
||||
eventdauer.kalender_id,
|
||||
eventdauer.von,
|
||||
eventdauer.bis,
|
||||
vonstunde.stunde AS vonstunde_nr,
|
||||
bisstunde.stunde AS bisstunde_nr
|
||||
FROM eventdauer
|
||||
LEFT JOIN lehre.tbl_stunde vonstunde ON eventdauer.von::time = vonstunde.beginn
|
||||
LEFT JOIN lehre.tbl_stunde bisstunde ON eventdauer.bis::time = bisstunde.ende
|
||||
),
|
||||
unterrichtsdauer AS (
|
||||
SELECT
|
||||
raster.lehreinheit_id,
|
||||
raster.kalender_id,
|
||||
sum(EXTRACT(EPOCH FROM (LEAST(stunde.ende, raster.bis::time) - GREATEST(stunde.beginn, raster.von::time))) / 60) AS minuten
|
||||
FROM raster JOIN lehre.tbl_stunde stunde ON stunde.beginn < raster.bis::time AND stunde.ende > raster.von::time
|
||||
WHERE raster.vonstunde_nr IS NULL OR raster.bisstunde_nr IS NULL
|
||||
GROUP BY raster.lehreinheit_id, raster.kalender_id
|
||||
),
|
||||
verplantestunden AS (
|
||||
SELECT raster.lehreinheit_id, raster.kalender_id,
|
||||
CASE
|
||||
WHEN raster.vonstunde_nr IS NOT NULL AND raster.bisstunde_nr IS NOT NULL
|
||||
THEN (raster.bisstunde_nr - raster.vonstunde_nr + 1)
|
||||
ELSE coalesce(unterrichtsdauer.minuten, 0) / 45
|
||||
END AS verplantestunde
|
||||
FROM raster LEFT JOIN unterrichtsdauer ON unterrichtsdauer.lehreinheit_id = raster.lehreinheit_id AND unterrichtsdauer.kalender_id = raster.kalender_id
|
||||
),
|
||||
verplant_gesamt AS (
|
||||
SELECT lehreinheit_id, SUM(verplantestunde) AS verplant
|
||||
FROM verplantestunden
|
||||
GROUP BY lehreinheit_id
|
||||
)
|
||||
SELECT
|
||||
lema.lehreinheit_id,
|
||||
lema.mitarbeiter_uid,
|
||||
lema.planstunden,
|
||||
ROUND(coalesce(verplant_gesamt.verplant, 0), 2) AS verplant,
|
||||
ROUND((lema.planstunden - coalesce(verplant_gesamt.verplant, 0)), 2) AS offenestunden
|
||||
FROM lehre.tbl_lehreinheitmitarbeiter lema
|
||||
LEFT JOIN verplant_gesamt ON verplant_gesamt.lehreinheit_id = lema.lehreinheit_id
|
||||
WHERE lema.lehreinheit_id IN ($placeholders)";
|
||||
|
||||
return $db->execReadOnlyQuery($qry, array_merge($lehreinheit_ids, $lehreinheit_ids));
|
||||
}
|
||||
|
||||
private function getLVTmp()
|
||||
{
|
||||
return "SELECT DISTINCT ON(lehrveranstaltung_id) *,
|
||||
$qry = "SELECT DISTINCT ON(lehrveranstaltung_id) *,
|
||||
'' as stundenblockung,
|
||||
'' as lehreinheit_id,
|
||||
'' as wochenrythmus,
|
||||
@@ -468,33 +408,24 @@ EOSQL;
|
||||
'' as studienplan_beeichnung,
|
||||
UPPER(CONCAT(vw_lehreinheit.stg_typ, vw_lehreinheit.stg_kurzbz)) as studiengang
|
||||
FROM campus.vw_lehreinheit
|
||||
WHERE studiensemester_kurzbz = ? ";
|
||||
WHERE mitarbeiter_uid = ?
|
||||
AND studiensemester_kurzbz = ?";
|
||||
|
||||
if (!is_null($stg_kz)) {
|
||||
$qry .= " AND lv_studiengang_kz = ?";
|
||||
}
|
||||
|
||||
return $qry;
|
||||
}
|
||||
|
||||
public function getLvsById($lehrveranstaltung_id, $studiensemester_kurzbz)
|
||||
{
|
||||
$where = ' AND lehrveranstaltung_id = ?';
|
||||
$qry = "WITH lvs AS (" . $this->getLVTmp() . $where .")
|
||||
SELECT lvs.*
|
||||
FROM lvs
|
||||
";
|
||||
|
||||
$params = array($studiensemester_kurzbz, $lehrveranstaltung_id);
|
||||
return $this->execReadOnlyQuery($qry, $params);
|
||||
}
|
||||
public function getLvsByEmployee($mitarbeiter_uid, $studiensemester_kurzbz, $stg_kz = null)
|
||||
{
|
||||
$where = ' AND mitarbeiter_uid = ?';
|
||||
|
||||
if ($stg_kz != null)
|
||||
$where .= " AND lv_studiengang_kz = ?";
|
||||
|
||||
$qry = "WITH lvs AS (" . $this->getLVTmp() . $where .")
|
||||
$qry = "WITH lvs AS (" . $this->getLVTmp($stg_kz) . ")
|
||||
SELECT lvs.*
|
||||
FROM lvs";
|
||||
|
||||
$params = array($studiensemester_kurzbz, $mitarbeiter_uid);
|
||||
FROM lvs
|
||||
";
|
||||
|
||||
$params = array($mitarbeiter_uid, $studiensemester_kurzbz);
|
||||
if (!is_null($stg_kz))
|
||||
{
|
||||
$params[] = $stg_kz;
|
||||
@@ -701,36 +632,6 @@ EOSQL;
|
||||
return success('Contract successfully updated.');
|
||||
}
|
||||
|
||||
private function _getLeCTE()
|
||||
{
|
||||
return $this->_getGruppenCTE() . ",
|
||||
". $this->_getLektorenCTE() . ",
|
||||
". $this->_getFachbereichCTE() . ",
|
||||
". $this->_getTagsCTE() . "
|
||||
|
||||
SELECT lehreinheiten.*,
|
||||
lehreinheiten.lehrform_kurzbz as lv_lehrform_kurzbz,
|
||||
tbl_lehrveranstaltung.kurzbz as lv_kurzbz,
|
||||
tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
|
||||
COALESCE(tag_data_agg.tags, '[]'::json) AS tags,
|
||||
gruppen.gruppen,
|
||||
mitarbeiter.lektoren,
|
||||
mitarbeiter.le_planstunden,
|
||||
mitarbeiter.vorname,
|
||||
mitarbeiter.nachname,
|
||||
mitarbeiter.semesterstunden,
|
||||
fachbereich.bezeichnung as fachbereich,
|
||||
UPPER(CONCAT(tbl_studiengang.typ,tbl_studiengang.kurzbz)) as studiengang,
|
||||
semester
|
||||
FROM lehreinheiten
|
||||
LEFT JOIN lehre.tbl_lehrveranstaltung ON tbl_lehrveranstaltung.lehrveranstaltung_id = lehreinheiten.lehrfach_id
|
||||
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
LEFT JOIN tag_data_agg ON tag_data_agg.lehreinheit_id = lehreinheiten.lehreinheit_id
|
||||
LEFT JOIN mitarbeiter ON lehreinheiten.lehreinheit_id = mitarbeiter.lehreinheit_id
|
||||
LEFT JOIN fachbereich ON lehreinheiten.lehreinheit_id = fachbereich.lehreinheit_id
|
||||
LEFT JOIN gruppen ON lehreinheiten.lehreinheit_id = gruppen.lehreinheit_id
|
||||
WHERE true";
|
||||
}
|
||||
private function _getGruppenCTE()
|
||||
{
|
||||
return "gruppen AS (
|
||||
|
||||
@@ -292,29 +292,13 @@ class Studiensemester_model extends DB_Model
|
||||
COALESCE(
|
||||
(SELECT studiensemester_kurzbz FROM semester_interval_startdate),
|
||||
(SELECT studiensemester_kurzbz FROM nearest_semester_interval_startdate)
|
||||
) AS studiensemester_kurzbz,
|
||||
COALESCE(
|
||||
(SELECT start FROM semester_interval_startdate),
|
||||
(SELECT start FROM nearest_semester_interval_startdate)
|
||||
) as start,
|
||||
COALESCE(
|
||||
(SELECT ende FROM semester_interval_startdate),
|
||||
(SELECT ende FROM nearest_semester_interval_startdate)
|
||||
) as ende
|
||||
) AS studiensemester_kurzbz
|
||||
UNION
|
||||
SELECT
|
||||
COALESCE(
|
||||
(SELECT studiensemester_kurzbz FROM semester_interval_enddate),
|
||||
(SELECT studiensemester_kurzbz FROM nearest_semester_interval_enddate)
|
||||
) AS studiensemester_kurzbz,
|
||||
COALESCE(
|
||||
(SELECT start FROM semester_interval_enddate),
|
||||
(SELECT start FROM nearest_semester_interval_enddate)
|
||||
) as start,
|
||||
COALESCE(
|
||||
(SELECT ende FROM semester_interval_enddate),
|
||||
(SELECT ende FROM nearest_semester_interval_enddate)
|
||||
) as ende
|
||||
) AS studiensemester_kurzbz
|
||||
|
||||
EOSQL;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ $includesArray = array(
|
||||
'primevue3' => true,
|
||||
'skipID' => '#fhccontent',
|
||||
'vuedatepicker11' => true,
|
||||
'highcharts' => true,
|
||||
'customCSSs' => array(
|
||||
'public/css/components/verticalsplit.css',
|
||||
'public/css/components/searchbar/searchbar.css',
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
$this->load->view(
|
||||
'templates/FHC-Header',
|
||||
array(
|
||||
'title' => 'FH-Complete',
|
||||
'bootstrap5' => true,
|
||||
'fontawesome6' => true,
|
||||
'axios027' => true,
|
||||
'restclient' => true,
|
||||
'vue3' => true,
|
||||
'tabulator6' => true,
|
||||
'primevue3' => true,
|
||||
'vuedatepicker11' => true,
|
||||
'customJSs' => [
|
||||
'vendor/npm-asset/primevue/colorpicker/colorpicker.js',
|
||||
'vendor/moment/luxonjs/luxon.min.js'
|
||||
],
|
||||
'customJSModules' => ['public/js/apps/Dashboard/WidgetAdmin.js'],
|
||||
'customCSSs' => [
|
||||
'public/css/Fhc.css',
|
||||
'public/css/components/verticalsplit.css',
|
||||
'public/css/components/dashboard.css',
|
||||
'public/css/components/primevue.css',
|
||||
'public/css/components/vue-datepicker.css',
|
||||
],
|
||||
'navigationcomponent' => true
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div id="main"></div>
|
||||
|
||||
<?php $this->load->view('templates/FHC-Footer'); ?>
|
||||
@@ -21,7 +21,6 @@ $this->load->view('templates/FHC-Header', $includesArray);
|
||||
?>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.body.classList.add("cis4");
|
||||
if (window.self !== window.top)
|
||||
document.body.classList.add("in-frame");
|
||||
</script>
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
* Manuela Thamer <[email protected]>
|
||||
*/
|
||||
require_once(dirname(__FILE__).'/basis_db.class.php');
|
||||
require_once(dirname(__FILE__).'/mitarbeiter.class.php');
|
||||
|
||||
class entwicklungsteam extends basis_db
|
||||
{
|
||||
@@ -44,6 +43,7 @@ class entwicklungsteam extends basis_db
|
||||
public $ext_id;
|
||||
|
||||
public $besqual;
|
||||
public $studiengang_kz_old;
|
||||
|
||||
/**
|
||||
* Konstruktor
|
||||
@@ -74,7 +74,7 @@ class entwicklungsteam extends basis_db
|
||||
$qry = "SELECT * FROM bis.tbl_entwicklungsteam JOIN bis.tbl_besqual USING(besqualcode)
|
||||
WHERE entwicklungsteam_id=".$this->db_add_param($entwicklungsteam_id);
|
||||
|
||||
$qry.=";";
|
||||
$qry.=";";
|
||||
|
||||
if($this->db_query($qry))
|
||||
{
|
||||
@@ -147,11 +147,6 @@ class entwicklungsteam extends basis_db
|
||||
$this->errormsg = 'Es muss ein Mitarbeiter angegeben werden';
|
||||
return false;
|
||||
}
|
||||
if(!$this->checkIfExistingUid($this->mitarbeiter_uid))
|
||||
{
|
||||
$this->errormsg = 'Bitte einen Eintrag aus dem Dropdown Personensuche auswählen!';
|
||||
return false;
|
||||
}
|
||||
if($this->studiengang_kz=='')
|
||||
{
|
||||
$this->errormsg = 'Es muss ein Studiengang angegeben werden';
|
||||
@@ -167,26 +162,6 @@ class entwicklungsteam extends basis_db
|
||||
$this->errormsg = 'Endedatum darf nicht vor Anfangsdatum liegen';
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->beginn != '')
|
||||
{
|
||||
$beginnParts = explode('-', $this->beginn);
|
||||
if (!checkdate((int)$beginnParts[1], (int)$beginnParts[2], (int)$beginnParts[0])) {
|
||||
$this->errormsg = 'Anfangsdatum ist ungültig';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if($this->ende != '')
|
||||
{
|
||||
$endeParts = explode('-', $this->ende);
|
||||
if (!checkdate((int)$endeParts[1], (int)$endeParts[2], (int)$endeParts[0]))
|
||||
{
|
||||
$this->errormsg = 'Endedatum ist ungültig';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -208,28 +183,30 @@ class entwicklungsteam extends basis_db
|
||||
//Neuen Datensatz anlegen
|
||||
$qry = "INSERT INTO bis.tbl_entwicklungsteam (mitarbeiter_uid, studiengang_kz, besqualcode, beginn, ende,
|
||||
updateamum, updatevon, insertamum, insertvon) VALUES (".
|
||||
$this->db_add_param($this->mitarbeiter_uid).', '.
|
||||
$this->db_add_param($this->studiengang_kz, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->besqualcode, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->beginn).', '.
|
||||
$this->db_add_param($this->ende).', '.
|
||||
$this->db_add_param($this->updateamum).', '.
|
||||
$this->db_add_param($this->updatevon).', '.
|
||||
$this->db_add_param($this->insertamum).', '.
|
||||
$this->db_add_param($this->insertvon).');';
|
||||
$this->db_add_param($this->mitarbeiter_uid).', '.
|
||||
$this->db_add_param($this->studiengang_kz, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->besqualcode, FHC_INTEGER).', '.
|
||||
$this->db_add_param($this->beginn).', '.
|
||||
$this->db_add_param($this->ende).', '.
|
||||
$this->db_add_param($this->updateamum).', '.
|
||||
$this->db_add_param($this->updatevon).', '.
|
||||
$this->db_add_param($this->insertamum).', '.
|
||||
$this->db_add_param($this->insertvon).');';
|
||||
}
|
||||
else
|
||||
{
|
||||
if($this->studiengang_kz_old=='')
|
||||
$this->studiengang_kz_old = $this->studiengang_kz;
|
||||
|
||||
//Bestehenden Datensatz aktualisieren
|
||||
$qry= "UPDATE bis.tbl_entwicklungsteam SET".
|
||||
" besqualcode=".$this->db_add_param($this->besqualcode, FHC_INTEGER).",".
|
||||
" beginn=".$this->db_add_param($this->beginn).",".
|
||||
" studiengang_kz=".$this->db_add_param($this->studiengang_kz, FHC_INTEGER).",".
|
||||
" ende=".$this->db_add_param($this->ende).",".
|
||||
" mitarbeiter_uid=".$this->db_add_param($this->mitarbeiter_uid).",".
|
||||
" updateamum=".$this->db_add_param($this->updateamum).",".
|
||||
" updatevon=".$this->db_add_param($this->updatevon).
|
||||
" WHERE entwicklungsteam_id=".$this->db_add_param($this->entwicklungsteam_id).";";
|
||||
" besqualcode=".$this->db_add_param($this->besqualcode, FHC_INTEGER).",".
|
||||
" beginn=".$this->db_add_param($this->beginn).",".
|
||||
" studiengang_kz=".$this->db_add_param($this->studiengang_kz, FHC_INTEGER).",".
|
||||
" ende=".$this->db_add_param($this->ende).",".
|
||||
" updateamum=".$this->db_add_param($this->updateamum).",".
|
||||
" updatevon=".$this->db_add_param($this->updatevon).
|
||||
" WHERE entwicklungsteam_id=".$this->db_add_param($this->entwicklungsteam_id).";";
|
||||
}
|
||||
|
||||
if($this->db_query($qry))
|
||||
@@ -257,7 +234,7 @@ class entwicklungsteam extends basis_db
|
||||
if($studiengang_kz!=null)
|
||||
$qry.=" AND studiengang_kz=".$this->db_add_param($studiengang_kz);
|
||||
|
||||
$qry.=";";
|
||||
$qry.=";";
|
||||
|
||||
if($this->db_query($qry))
|
||||
{
|
||||
@@ -339,7 +316,7 @@ class entwicklungsteam extends basis_db
|
||||
|
||||
if ($sort != null)
|
||||
{
|
||||
$qry .= " ORDER BY ".$sort;
|
||||
$qry .= " ORDER BY ".$sort;
|
||||
}
|
||||
|
||||
$qry .= ";";
|
||||
@@ -387,7 +364,7 @@ class entwicklungsteam extends basis_db
|
||||
$bismeldung_jahr = $datetime->format('Y');
|
||||
|
||||
//laden des Datensatzes
|
||||
$qry = "SELECT tbl_entwicklungsteam.*, tbl_besqual.*, tbl_studiengang.studiengang_kz, tbl_studiengang.melderelevant
|
||||
$qry = "SELECT tbl_entwicklungsteam.*, tbl_besqual.*, tbl_studiengang.studiengang_kz, tbl_studiengang.melderelevant
|
||||
FROM bis.tbl_entwicklungsteam
|
||||
JOIN bis.tbl_besqual USING(besqualcode)
|
||||
JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
@@ -398,7 +375,7 @@ class entwicklungsteam extends basis_db
|
||||
if($studiengang_kz!=null)
|
||||
$qry.=" AND studiengang_kz=".$this->db_add_param($studiengang_kz);
|
||||
|
||||
$qry.=";";
|
||||
$qry.=";";
|
||||
|
||||
if($this->db_query($qry))
|
||||
{
|
||||
@@ -430,20 +407,5 @@ class entwicklungsteam extends basis_db
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Überprüft, ob die übergebene Uid in der Tabelle tbl_mitarbeiter vorhanden ist
|
||||
* @param String $uid UID des Mitarbeiters
|
||||
* @return true wenn vorhanden, false wenn nicht
|
||||
*/
|
||||
private function checkIfExistingUid($uid)
|
||||
{
|
||||
$mitarbeiter = new mitarbeiter();
|
||||
|
||||
if(!$mitarbeiter->checkIfExistingUid($uid))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1995,23 +1995,5 @@ class mitarbeiter extends benutzer
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prueft ob eine existierende ma_uid übergeben wurde
|
||||
* @param String $mitarbeiter_uid mitarbeiter_uid
|
||||
* @return bool True wenn mitarbeiter_uid vorhanden sonst false.
|
||||
*/
|
||||
public function checkIfExistingUid($mitarbeiter_uid)
|
||||
{
|
||||
$qry = "Select 1 from public.tbl_mitarbeiter where mitarbeiter_uid =".$this->db_add_param($mitarbeiter_uid);
|
||||
|
||||
if ($result = $this->db_query($qry))
|
||||
{
|
||||
if ($this->db_num_rows($result) > 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
+4
-25
@@ -7,11 +7,6 @@ html {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
body.compat,
|
||||
body.compat main#cis-main {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.fhc-dark-bg{
|
||||
background-color: var(--fhc-dark);
|
||||
color:var(--fhc-light) !important;
|
||||
@@ -459,8 +454,8 @@ body.compat main#cis-main {
|
||||
|
||||
/* desktop */
|
||||
@media (min-width: 992px) {
|
||||
body.cis4 {
|
||||
display: flex;
|
||||
body {
|
||||
display: flex;
|
||||
padding-top: var(--fhc-cis-header-height);
|
||||
width: calc(100svw - var(--scrollbar-width, 0px));
|
||||
/* overflow: visible !important; */
|
||||
@@ -765,7 +760,7 @@ body.compat main#cis-main {
|
||||
}
|
||||
|
||||
.dashboard-item {
|
||||
overscroll-behavior: auto;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
.abgabe-zieldatum-border-red,
|
||||
@@ -858,20 +853,4 @@ body.compat main#cis-main {
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.cis-main-menu {
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.cis-main-menu ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cis-main-menu ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
+25
-2
@@ -1,3 +1,26 @@
|
||||
.fhc-page-grid {
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid: "header header" auto "nav main" 1fr / auto 1fr;
|
||||
}
|
||||
.fhc-page-grid > * {
|
||||
grid-area: header;
|
||||
}
|
||||
.fhc-page-grid > .navbar-left-side {
|
||||
grid-area: nav;
|
||||
position: initial;
|
||||
align-items: start;
|
||||
}
|
||||
.fhc-page-grid > .content {
|
||||
grid-area: main;
|
||||
margin: 0;
|
||||
}
|
||||
@media (max-width: 992px) {
|
||||
.fhc-page-grid {
|
||||
grid: "header main" auto "nav main" 1fr / auto 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fhc-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -100,10 +123,10 @@
|
||||
}
|
||||
|
||||
.modal-msg {
|
||||
max-width: 1378px;
|
||||
max-width: 1378px;
|
||||
}
|
||||
.modal-msg .modal-content {
|
||||
height: 95vh;
|
||||
height: 95vh;
|
||||
}
|
||||
|
||||
.fhcSkipLink {
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
}
|
||||
|
||||
/* special bootstrap5 styling for tableWidget and their accordion-item ::after content */
|
||||
.accordion-button::after{
|
||||
.tabulator .accordion-button::after{
|
||||
content:none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
}
|
||||
|
||||
/* special bootstrap5 styling for tableWidget and their accordion-item ::after content */
|
||||
.accordion-button::after{
|
||||
.tabulator .accordion-button::after{
|
||||
content:none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -714,6 +714,10 @@
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
.input-group-text .p-colorpicker-preview {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.p-colorpicker-panel {
|
||||
background: #212529;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="rgba(139,179,29,1)" />
|
||||
<stop offset="100%" stop-color="rgba(114,120,122,0)" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="90" y="322" width="80" height="90" fill="url(#g)" stroke="#72777A" stroke-width="3" />
|
||||
<rect x="216" y="212" width="80" height="200" fill="url(#g)" stroke="#72777A" stroke-width="3" />
|
||||
<rect x="342" y="100" width="80" height="312" fill="url(#g)" stroke="#72777A" stroke-width="3" />
|
||||
<path d="M75,370l150,-150l50,50l120,-120l-10,-10l45,-15l-15,45l-10,-10l-130,130l-50,-50l-140,140" fill="rgb(0, 100, 156)" stroke="rgb(0, 100, 156)" stroke-width="5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 739 B |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<clipPath id="c">
|
||||
<path d="M92,320A1,1,0,0,1,420,192l41,-16A1,1,0,1,0,51,336z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<text x="256" style="fill: rgb(0, 100, 156); font-family: "Arial", sans-serif; font-size: 150px; font-weight: 700;text-anchor: middle;" y="300">KPI</text>
|
||||
<foreignObject width="512" height="512" clip-path="url(#c)">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="width: 100%;height: 100%;background: conic-gradient(from -112deg, rgba(114,120,122,0),rgba(139,179,29,1));"></div>
|
||||
</foreignObject>
|
||||
<path fill="transparent" d="M92,320A1,1,0,0,1,420,192l41,-16A1,1,0,1,0,51,336" stroke="#72777A" stroke-width="3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 714 B |
@@ -34,6 +34,18 @@ export default {
|
||||
url: '/api/frontend/v1/dashboard/widget/listAllowed/' + dashboard
|
||||
};
|
||||
},
|
||||
listAll() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/dashboard/widget/listAll'
|
||||
};
|
||||
},
|
||||
listAllOriginal() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/dashboard/widget/listAllOriginal'
|
||||
};
|
||||
},
|
||||
setAllowed(dashboard_id, widget_id, allowed) {
|
||||
return {
|
||||
method: 'post',
|
||||
@@ -42,5 +54,32 @@ export default {
|
||||
dashboard_id, widget_id, allowed
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
create(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/dashboard/widget/create',
|
||||
params
|
||||
};
|
||||
},
|
||||
update(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/dashboard/widget/update',
|
||||
params
|
||||
};
|
||||
},
|
||||
remove(widget_id) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/dashboard/widget/remove',
|
||||
params: { widget_id }
|
||||
};
|
||||
},
|
||||
generators() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/dashboard/widget/generators'
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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/report/list',
|
||||
};
|
||||
},
|
||||
vars(statistik_kurzbz) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/report/vars/' + statistik_kurzbz,
|
||||
};
|
||||
},
|
||||
get(statistik_kurzbz, params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/report/get/' + statistik_kurzbz,
|
||||
params,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -28,5 +28,15 @@ export default {
|
||||
url: '/api/frontend/v1/organisation/StudiengangEP/getStudiengangByKz',
|
||||
params: { studiengang_kz }
|
||||
};
|
||||
}
|
||||
},
|
||||
getAllowed(permissions) {
|
||||
if (!Array.isArray(permissions))
|
||||
permissions = permissions.split(',');
|
||||
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/organisation/StudiengangEP/getAllowed',
|
||||
params: { permissions }
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 {
|
||||
getAktNext() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/organisation/studienjahr/getAktNext',
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -44,15 +44,5 @@ export default {
|
||||
studiensemester_kurzbz
|
||||
}
|
||||
};
|
||||
},
|
||||
getContainingOrNearestByDateRange(from, to) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/organisation/studiensemester/getContainingOrNearestByDateRange',
|
||||
params: {
|
||||
from,
|
||||
to,
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import FhcDashboard from '../../components/Dashboard/Dashboard.js';
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import PluginsCharts from '../../plugins/highchartsVue.js';
|
||||
import Theme from '../../plugins/Theme.js';
|
||||
import contrast from '../../directives/contrast.js';
|
||||
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
|
||||
@@ -348,6 +349,7 @@ app.use(PluginsPhrasen);
|
||||
app.use(Theme);
|
||||
app.directive('contrast', contrast);
|
||||
app.mount('#fhccontent');
|
||||
app.use(PluginsCharts);
|
||||
|
||||
router.afterEach((to, from, failure) => {
|
||||
app.config.globalProperties.$api.call(ApiRouteInfo.info('cis4', to.fullPath));
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { CoreNavigationCmpt } from '../../components/navigation/Navigation.js';
|
||||
import WidgetAdmin from '../../components/Dashboard/WidgetAdmin.js';
|
||||
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'WidgetsAdminApp',
|
||||
data: () => ({
|
||||
appSideMenuEntries: {}
|
||||
}),
|
||||
components: {
|
||||
CoreNavigationCmpt,
|
||||
WidgetAdmin
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone
|
||||
};
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="widgets-admin-app fhc-page-grid">
|
||||
<core-navigation-cmpt :add-side-menu-entries="appSideMenuEntries" />
|
||||
|
||||
<div class="content d-flex flex-column gap-3 w-100 h-100 overflow-hidden">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3">
|
||||
<h1 class="h2 pb-3 border-bottom mb-0 w-100">
|
||||
{{ $p.t('dashboard/widgets') || ' ' }}
|
||||
</h1>
|
||||
</div>
|
||||
<widget-admin class="h-100 overflow-hidden" />
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
app.use(primevue.config.default, {
|
||||
zIndex: {
|
||||
overlay: 9000,
|
||||
tooltip: 8000
|
||||
}
|
||||
})
|
||||
app.use(PluginsPhrasen);
|
||||
app.directive('tooltip', primevue.tooltip);
|
||||
app.mount('#main');
|
||||
@@ -17,8 +17,6 @@ const router = VueRouter.createRouter({
|
||||
component: LVVerwaltung,
|
||||
children: [
|
||||
{ name: 'emp', path: 'emp/:emp/:treemenu(.*)*', component: LVVerwaltung },
|
||||
{ name: 'le', path: 'le/:le', component: LVVerwaltung },
|
||||
{ name: 'lv', path: 'lv/:lv', component: LVVerwaltung },
|
||||
{ name: 'treemenu', path: ':treemenu(.*)*', component: LVVerwaltung }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -103,13 +103,6 @@ export default {
|
||||
keyboard: this.keyboard
|
||||
});
|
||||
},
|
||||
//to avoid pagelocks if more than one instance
|
||||
beforeUnmount() {
|
||||
if (this.modal) {
|
||||
this.modal.dispose();
|
||||
this.modal = null;
|
||||
}
|
||||
},
|
||||
popup(body, options, title, footer) {
|
||||
const BsModal = this,
|
||||
slots = {};
|
||||
|
||||
@@ -6,10 +6,16 @@ export const FhcChart = {
|
||||
type: Object,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
methods: {
|
||||
reflow() {
|
||||
if (this.$refs.chart?.chart)
|
||||
this.$refs.chart.chart.reflow();
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div style="width:100%;height:100%;overflow:auto">
|
||||
<figure>
|
||||
<highcharts class="chart" :options="chartOptions"></highcharts>
|
||||
<figure class="h-100 m-0">
|
||||
<highcharts ref="chart" class="chart h-100" :options="chartOptions"></highcharts>
|
||||
</figure>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -16,15 +16,9 @@ export default {
|
||||
return this.mode + '#' + this.path + '#' + this.query_string;
|
||||
}
|
||||
},
|
||||
beforeMount: function() {
|
||||
document.body.classList.add('compat');
|
||||
},
|
||||
mounted: function() {
|
||||
this.srcUrl = this.buildSrcUrl();
|
||||
},
|
||||
beforeUnmount: function() {
|
||||
document.body.classList.remove('compat');
|
||||
},
|
||||
watch: {
|
||||
propsWatchHelper: function() {
|
||||
if(this.lastLoadediFrameURL === '') {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import FhcCalendar from "../../Calendar/LvPlan.js";
|
||||
|
||||
import ApiLvPlan from '../../../api/factory/lvPlan.js';
|
||||
import ApiStudienSemester from '../../../api/factory/studiensemester.js';
|
||||
import ApiAuthinfo from '../../../api/factory/authinfo.js';
|
||||
|
||||
export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week";
|
||||
@@ -17,12 +16,13 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
studiensemester_kurzbz: null,
|
||||
studiensemester_start: null,
|
||||
studiensemester_ende: null,
|
||||
uid: null,
|
||||
isMitarbeiter: false,
|
||||
isStudent: false,
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
|
||||
startSemester: null,
|
||||
endSemester: null,
|
||||
};
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
@@ -48,12 +48,9 @@ export default {
|
||||
return this.propsViewData?.mode;
|
||||
},
|
||||
downloadLinks() {
|
||||
const startDate = this.startSemester?.start;
|
||||
const endDate = this.endSemester ? this.endSemester.ende : this.startSemester?.ende;
|
||||
|
||||
if (!startDate || !endDate || !this.uid)
|
||||
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
|
||||
return false;
|
||||
|
||||
|
||||
let type = false;
|
||||
type = this.isStudent ? 'student' : type;
|
||||
type = this.isMitarbeiter ? 'lektor' : type;
|
||||
@@ -64,10 +61,10 @@ export default {
|
||||
|
||||
const opts = { zone: this.timezone };
|
||||
const start = luxon.DateTime
|
||||
.fromISO(startDate, opts)
|
||||
.fromISO(this.studiensemester_start, opts)
|
||||
.toUnixInteger();
|
||||
const ende = luxon.DateTime
|
||||
.fromISO(endDate, opts)
|
||||
.fromISO(this.studiensemester_ende, opts)
|
||||
.toUnixInteger();
|
||||
|
||||
const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
@@ -112,21 +109,16 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
async updateRange(rangeInterval) {
|
||||
const semesterResponse = await this.$api.call(
|
||||
ApiStudienSemester.getContainingOrNearestByDateRange(
|
||||
rangeInterval.s.toISO().slice(0, 10),
|
||||
rangeInterval.e.toISO().slice(0, 10),
|
||||
),
|
||||
);
|
||||
this.startSemester =
|
||||
semesterResponse.data.length
|
||||
? semesterResponse.data[0]
|
||||
: null;
|
||||
this.endSemester =
|
||||
semesterResponse.data.length > 1
|
||||
? semesterResponse.data[1]
|
||||
: null;
|
||||
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 [
|
||||
@@ -150,11 +142,8 @@ export default {
|
||||
<div class="cis-lvplan-personal d-flex flex-column h-100">
|
||||
<h2>
|
||||
{{ $p.t('lehre/stundenplan') }}
|
||||
<span v-if="startSemester?.studiensemester_kurzbz">
|
||||
{{ startSemester.studiensemester_kurzbz }}
|
||||
<span v-if="endSemester?.studiensemester_kurzbz">
|
||||
{{ "- " + endSemester.studiensemester_kurzbz }}
|
||||
</span>
|
||||
<span v-if="studiensemester_kurzbz" class="ps-3">
|
||||
{{ studiensemester_kurzbz }}
|
||||
</span>
|
||||
</h2>
|
||||
<hr>
|
||||
|
||||
@@ -5,7 +5,6 @@ import FhcCalendar from "../../Calendar/LvPlan.js";
|
||||
import ApiLvPlan from "../.././../api/factory/lvPlan.js";
|
||||
import ApiOtherLvPlan from "../.././../api/factory/otherLvPlan.js";
|
||||
import ApiAuthinfo from "../../../api/factory/authinfo.js";
|
||||
import ApiStudienSemester from '../../../api/factory/studiensemester.js';
|
||||
|
||||
export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week";
|
||||
export const DEFAULT_MODE_LVPLAN_MOBILE = "List";
|
||||
@@ -23,6 +22,9 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
localProps: {},
|
||||
studiensemester_kurzbz: null,
|
||||
studiensemester_start: null,
|
||||
studiensemester_ende: null,
|
||||
isOtherPersonMitarbeiter: false,
|
||||
isOtherPersonStudent: false,
|
||||
currentStgBezeichnung: null,
|
||||
@@ -34,8 +36,6 @@ export default {
|
||||
photo: "",
|
||||
},
|
||||
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
|
||||
startSemester: null,
|
||||
endSemester: null,
|
||||
};
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
@@ -67,10 +67,11 @@ export default {
|
||||
// do not show download links in otherLvPlan Mode
|
||||
return false;
|
||||
|
||||
const startDate = this.startSemester?.start;
|
||||
const endDate = this.endSemester ? this.endSemester.ende : this.startSemester?.ende;
|
||||
|
||||
if (!startDate || !endDate || !this.uid)
|
||||
if (
|
||||
!this.studiensemester_start ||
|
||||
!this.studiensemester_ende ||
|
||||
!this.propsViewData.otherUid
|
||||
)
|
||||
return false;
|
||||
|
||||
const type = this.isOtherPersonStudent
|
||||
@@ -83,11 +84,11 @@ export default {
|
||||
|
||||
const opts = { zone: this.timezone };
|
||||
const start = luxon.DateTime.fromISO(
|
||||
startDate,
|
||||
this.studiensemester_start,
|
||||
opts,
|
||||
).toUnixInteger();
|
||||
const ende = luxon.DateTime.fromISO(
|
||||
endDate,
|
||||
this.studiensemester_ende,
|
||||
opts,
|
||||
).toUnixInteger();
|
||||
|
||||
@@ -165,21 +166,19 @@ export default {
|
||||
},
|
||||
});
|
||||
},
|
||||
async updateRange(rangeInterval) {
|
||||
const semesterResponse = await this.$api.call(
|
||||
ApiStudienSemester.getContainingOrNearestByDateRange(
|
||||
rangeInterval.s.toISO().slice(0, 10),
|
||||
rangeInterval.e.toISO().slice(0, 10),
|
||||
),
|
||||
);
|
||||
this.startSemester =
|
||||
semesterResponse.data.length
|
||||
? semesterResponse.data[0]
|
||||
: null;
|
||||
this.endSemester =
|
||||
semesterResponse.data.length > 1
|
||||
? semesterResponse.data[1]
|
||||
: null;
|
||||
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 [
|
||||
@@ -251,14 +250,8 @@ export default {
|
||||
<div class="d-flex flex-column h-100">
|
||||
<h2>
|
||||
<div class="d-flex flex-row justify-content-between align-items-center">
|
||||
<span>
|
||||
{{ $p.t('lehre/stundenplan') }}
|
||||
<span v-if="startSemester?.studiensemester_kurzbz">
|
||||
{{ startSemester.studiensemester_kurzbz }}
|
||||
<span v-if="endSemester?.studiensemester_kurzbz">
|
||||
{{ "- " + endSemester.studiensemester_kurzbz }}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{{ $p.t('lehre/stundenplan') + (studiensemester_kurzbz ? " " + studiensemester_kurzbz : "") }}
|
||||
</span>
|
||||
<div @click="this.$router.push({name: 'ProfilView', params: {uid: propsViewData.otherUid}})" type="button" class="d-flex flex-row align-items-center gap-3">
|
||||
<span v-if="otherPersonData.fullNameWithTitle?.length">
|
||||
|
||||
@@ -186,7 +186,7 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="nav-main" class="cis-main-menu offcanvas offcanvas-start" tabindex="-1" aria-labelledby="nav-main-btn" data-bs-backdrop="false">
|
||||
<nav id="nav-main" class="offcanvas offcanvas-start" tabindex="-1" aria-labelledby="nav-main-btn" data-bs-backdrop="false">
|
||||
<div id="nav-main-sticky">
|
||||
<div class="d-flex flex-row h-100">
|
||||
<div class="offcanvas-body p-0">
|
||||
|
||||
@@ -162,7 +162,6 @@ export default {
|
||||
<div class="btn-group w-100">
|
||||
<a :target="target"
|
||||
:href="(entry.menu_open && hasFullLink) ? entry.url : null"
|
||||
draggable="false"
|
||||
@click="toggleCollapse"
|
||||
:class="{
|
||||
'btn btn-default rounded-0 text-start': true,
|
||||
@@ -181,15 +180,12 @@ export default {
|
||||
</div>
|
||||
<ul ref="children"
|
||||
class="nav w-100 collapse">
|
||||
<li>
|
||||
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeContent" v-for="child in entry.childs" :key="child" :entry="child" :level="level + 1"/>
|
||||
</li>
|
||||
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeContent" v-for="child in entry.childs" :key="child" :entry="child" :level="level + 1"/>
|
||||
</ul>
|
||||
</template>
|
||||
<a v-else
|
||||
:href="entry.url"
|
||||
:target="target"
|
||||
draggable="false"
|
||||
:class="{
|
||||
'btn btn-default rounded-0 w-100 text-start': true,
|
||||
['btn-level-' + level]: true,
|
||||
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
// check if the lv has lvplan entries for this studiensemester
|
||||
if (this.studiensemester && this.event) {
|
||||
return this.$fhcApi.factory.studium.getLvPlanForStudiensemester(this.studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id)
|
||||
.then(data => data.data?.retval)
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
if (Array.isArray(res) && res.length > 0) {
|
||||
this.hasLvPlanEintraege = true;
|
||||
@@ -114,4 +114,4 @@ export default {
|
||||
|
||||
|
||||
`,
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Feiertag-CalendarEvent',
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { formatDate } from "../../../../helpers/DateHelpers.js"
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Feiertag-ModalContent',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Feiertag-ModalTitle',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Lehreinheit-CalendarEvent',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -5,7 +5,6 @@ import ApiLvPlan from '../../../../api/factory/lvPlan.js';
|
||||
import ApiAddons from '../../../../api/factory/addons.js';
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Lehreinheit-ModalContent',
|
||||
components:{
|
||||
LvMenu,
|
||||
},
|
||||
@@ -50,16 +49,6 @@ export default {
|
||||
return this.event.ende;
|
||||
}
|
||||
return numberPadding(this.event.ende.getHours()) + ":" + numberPadding(this.event.ende.getMinutes());
|
||||
},
|
||||
showLvMenu: function() {
|
||||
const hideOnRoutes = [
|
||||
'OtherLvPlan',
|
||||
'StgOrgLvPlan'
|
||||
];
|
||||
if(hideOnRoutes.includes(this.$route.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -173,6 +162,6 @@ export default {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<lv-menu v-if="lvMenu.length && showLvMenu" :containerStyles="['p-0']" :rowStyles="['m-0']" :menu="lvMenu" />
|
||||
<lv-menu v-if="lvMenu.length && $route.name === 'MyLvPlan'" :containerStyles="['p-0']" :rowStyles="['m-0']" :menu="lvMenu" />
|
||||
</div>`,
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Lehreinheit-ModalTitle',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Reservierung-CalendarEvent',
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { numberPadding, formatDate } from "../../../../helpers/DateHelpers.js"
|
||||
import LvMenu from "../../Mylv/LvMenu.js";
|
||||
|
||||
export default {
|
||||
name: 'Cis-Renderer-Reservierung-ModalContent',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export default {
|
||||
name: 'Cis-Renderer-Reservierung-ModalTitle',
|
||||
props:{
|
||||
event: {
|
||||
type: Object,
|
||||
|
||||
@@ -145,8 +145,11 @@ export default {
|
||||
handleHideBsModal() {
|
||||
this.$emit('configClosed')
|
||||
},
|
||||
createTmpConfig() {
|
||||
this.tmpConfig = JSON.parse(JSON.stringify(this.arguments));
|
||||
},
|
||||
openConfig() {
|
||||
this.tmpConfig = { ...this.arguments };
|
||||
this.createTmpConfig();
|
||||
this.$refs.config.show();
|
||||
},
|
||||
setConfig(hasConfig) {
|
||||
@@ -163,7 +166,7 @@ export default {
|
||||
},
|
||||
sendChangeConfig(config) {
|
||||
for (var k in config) {
|
||||
if (this.widgetTemplate.arguments[k] == config[k]) {
|
||||
if (JSON.stringify(this.widgetTemplate.arguments[k]) == JSON.stringify(config[k])) {
|
||||
delete config[k];
|
||||
}
|
||||
}
|
||||
@@ -184,14 +187,14 @@ export default {
|
||||
this.$options.components["widget" + this.widgetTemplate.widget_id] = component;
|
||||
this.component = "widget" + this.widgetTemplate.widget_id;
|
||||
this.arguments = { ...this.widgetTemplate.arguments, ...this.config };
|
||||
this.tmpConfig = { ...this.arguments };
|
||||
this.createTmpConfig();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config() {
|
||||
this.arguments = { ...this.widgetTemplate?.arguments, ...this.config };
|
||||
this.tmpConfig = { ...this.arguments };
|
||||
this.createTmpConfig();
|
||||
this.$refs.config && this.$refs.config.hide();
|
||||
this.isLoading = false;
|
||||
},
|
||||
@@ -342,7 +345,7 @@ export default {
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<component
|
||||
:is="component"
|
||||
:is="hasConfig !== true ? hasConfig : component"
|
||||
v-if="ready && !isLoading"
|
||||
v-model:shared-data="sharedData"
|
||||
:config="tmpConfig"
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import VerticalSplit from "../verticalsplit/verticalsplit.js";
|
||||
import WidgetNew from "./WidgetAdmin/New.js";
|
||||
import WidgetList from "./WidgetAdmin/List.js";
|
||||
import WidgetEdit from "./WidgetAdmin/Edit.js";
|
||||
|
||||
export default {
|
||||
name: 'WidgetAdmin',
|
||||
components: {
|
||||
VerticalSplit,
|
||||
WidgetNew,
|
||||
WidgetList,
|
||||
WidgetEdit,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
imageSrc(src) {
|
||||
if (!src)
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'skin/images/fh_technikum_wien_illustration_klein.png';
|
||||
else if (src[0] == '/')
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + src.substr(1);
|
||||
return src;
|
||||
},
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
originalData: null,
|
||||
currentData: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
unsavedProgress() {
|
||||
if (JSON.stringify(this.originalData) != JSON.stringify(this.currentData))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
select(data) {
|
||||
this.originalData = data;
|
||||
this.currentData = JSON.parse(JSON.stringify(data));
|
||||
},
|
||||
create(data) {
|
||||
data.arguments = {};
|
||||
this.currentData = JSON.parse(JSON.stringify(data));
|
||||
this.save();
|
||||
},
|
||||
remove(widget) {
|
||||
this.$refs.list.$refs.table.tabulator.deleteRow(widget.widget_id);
|
||||
this.originalData = this.currentData = null;
|
||||
},
|
||||
save() {
|
||||
this.select(this.currentData);
|
||||
this.$nextTick(() => this.$refs.list.updateOrAddDataAndSelect(this.originalData));
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="widgets-admin">
|
||||
<widget-new ref="new" @create="create" />
|
||||
<vertical-split class="h-100 position-relative" use-div-height>
|
||||
<template #top>
|
||||
<widget-list
|
||||
ref="list"
|
||||
:unsaved-progress="unsavedProgress"
|
||||
@new="$refs.new?.show"
|
||||
@select="select"
|
||||
/>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<widget-edit
|
||||
v-if="currentData"
|
||||
ref="edit"
|
||||
:key="currentData.widget_id"
|
||||
v-model="currentData"
|
||||
:original-data="originalData"
|
||||
:unsaved-progress="unsavedProgress"
|
||||
@remove="remove"
|
||||
@saved="save"
|
||||
/>
|
||||
<div v-else class="h-100 d-flex justify-content-center align-items-center">
|
||||
{{ $p.t('dashboard/no_widget_selected') }}
|
||||
</div>
|
||||
</template>
|
||||
</vertical-split>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import CoreForm from "../../Form/Form.js";
|
||||
import EditBasics from "./Edit/Basics.js";
|
||||
import EditSetup from "./Edit/Setup.js";
|
||||
import EditPermission from "./Edit/Permission.js";
|
||||
|
||||
import ApiWidget from "../../../api/factory/dashboard/widget.js";
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAdminEdit',
|
||||
components: {
|
||||
CoreForm,
|
||||
EditBasics,
|
||||
EditSetup,
|
||||
EditPermission,
|
||||
},
|
||||
props: {
|
||||
originalData: Object,
|
||||
modelValue: Object,
|
||||
unsavedProgress: Boolean,
|
||||
},
|
||||
emits: [
|
||||
"saved",
|
||||
"remove",
|
||||
"update:modelValue",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
generator: null,
|
||||
saving: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(modelValue) {
|
||||
this.$emit('update:modelValue', modelValue);
|
||||
},
|
||||
},
|
||||
setup: {
|
||||
get() {
|
||||
return this.modelValue.setup;
|
||||
},
|
||||
set(setup) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, setup });
|
||||
},
|
||||
},
|
||||
permission: {
|
||||
get() {
|
||||
return this.modelValue.berechtigung_kurzbz;
|
||||
},
|
||||
set(berechtigung_kurzbz) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, berechtigung_kurzbz });
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'originalData.setup.generator': {
|
||||
async handler(gen) {
|
||||
if (!gen)
|
||||
return this.generator = null;
|
||||
|
||||
gen = gen.split(':').reverse();
|
||||
let file = '../../DashboardWidget/Generators/' + gen[0] + '.js';
|
||||
if (gen.length > 1)
|
||||
file = '../../../../extensions/' + gen[1] + '/js/components' + file.substr(5);
|
||||
|
||||
this.generator = Vue.markRaw((await import(file)).default);
|
||||
},
|
||||
immediate: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
this.saving = true;
|
||||
this.$refs.form.clearValidation();
|
||||
const data = JSON.parse(JSON.stringify(this.modelValue));
|
||||
this.$refs.form
|
||||
.call(ApiWidget.update(data))
|
||||
.then(() => {
|
||||
this.$emit('update:modelValue', data);
|
||||
this.$emit('saved');
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => this.saving = false);
|
||||
},
|
||||
remove() {
|
||||
this.$fhcAlert.confirmDelete()
|
||||
.then(result => {
|
||||
if (!result)
|
||||
return Promise.reject({ handled: true });
|
||||
|
||||
this.saving = true;
|
||||
return this.$api
|
||||
.call(ApiWidget.remove(this.modelValue.widget_id));
|
||||
})
|
||||
.then(() => {
|
||||
this.$emit('remove', this.modelValue);
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => this.saving = false);
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<core-form
|
||||
v-if="modelValue"
|
||||
ref="form"
|
||||
class="widgets-admin-edit mx-2 pb-5"
|
||||
@submit.prevent="save"
|
||||
>
|
||||
<edit-basics v-model="value" :original="originalData" class="border-bottom mb-3" />
|
||||
<template v-if="generator">
|
||||
<component
|
||||
:is="generator"
|
||||
v-model="value"
|
||||
:original="originalData"
|
||||
:key="modelValue.widget_id"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<edit-setup v-model="setup" class="border-bottom mb-3" />
|
||||
<edit-permission v-model="permission" />
|
||||
</template>
|
||||
<div class="position-absolute mx-2 my-1 bottom-0 end-0 z-3">
|
||||
<button
|
||||
v-if="generator"
|
||||
type="button"
|
||||
:disabled="saving"
|
||||
class="btn btn-danger me-2"
|
||||
@click="remove"
|
||||
>{{ $p.t('ui/loeschen') }}</button>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="saving || !unsavedProgress"
|
||||
class="btn btn-primary"
|
||||
>{{ $p.t('ui/speichern') }}</button>
|
||||
</div>
|
||||
</core-form>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import FormInput from "../../../Form/Input.js";
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAdminEditBasics',
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
inject: {
|
||||
imageSrc: "imageSrc",
|
||||
},
|
||||
props: {
|
||||
original: Object,
|
||||
modelValue: Object,
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
computed: {
|
||||
img() {
|
||||
return this.imageSrc(this.modelValue.setup?.icon);
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="widgets-admin-edit-basics">
|
||||
<div class="d-flex gap-3 my-2">
|
||||
<div class="col-auto">
|
||||
<img
|
||||
:src="img"
|
||||
class="object-fit-contain"
|
||||
style="height: calc(3em + 1.75rem + var(--bs-border-width) * 2 + 2 * var(--bs-body-font-size) * var(--bs-body-line-height)); width: calc(3em + 1.75rem + var(--bs-border-width) * 2 + 2 * var(--bs-body-font-size) * var(--bs-body-line-height))"
|
||||
>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="d-flex gap-3 mb-2">
|
||||
<div class="col">
|
||||
<b>{{ $p.t('dashboard/widget_kurzbz') }}: </b>
|
||||
{{ modelValue.widget_kurzbz }}
|
||||
</div>
|
||||
<div v-if="modelValue.widget_id !== undefined" class="col-auto">
|
||||
<b>{{ $p.t('dashboard/widget_id') }}: </b>
|
||||
{{ modelValue.widget_id }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<form-input
|
||||
:label="$p.t('global/beschreibung') + ':'"
|
||||
type="textarea"
|
||||
v-model="modelValue.beschreibung"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import FormInput from "../../../Form/Input.js";
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAdminEditPermission',
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: String,
|
||||
disabled: Boolean,
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue"
|
||||
],
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(modelValue) {
|
||||
this.$emit('update:modelValue', modelValue);
|
||||
},
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="widgets-admin-edit-permission">
|
||||
<form-input
|
||||
v-model="value"
|
||||
type="text"
|
||||
:label="$p.t('global/permission')"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import ReportAggregator from './Aggregators/Aggregator.js';
|
||||
|
||||
let counter = 0;
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportAggregators",
|
||||
components: {
|
||||
ReportAggregator,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
defaultAggregatorIndex: Vue.computed({
|
||||
get: () => this.defaultAggregatorIndex,
|
||||
set: v => {
|
||||
const curr = this.modelValue.find(agg => agg.default);
|
||||
if (curr)
|
||||
delete curr.default;
|
||||
this.modelValue[v].default = true;
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
myId: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
defaultAggregatorIndex() {
|
||||
return this.modelValue.findIndex(agg => agg.default);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addAggregator() {
|
||||
this.modelValue.push({});
|
||||
},
|
||||
removeAggregator(index) {
|
||||
this.$emit('update:modelValue', this.modelValue.toSpliced(index, 1));
|
||||
},
|
||||
getHeaderStyle(isDefault) {
|
||||
if (!isDefault)
|
||||
return undefined;
|
||||
return {
|
||||
'--bs-accordion-btn-bg': 'var(--bs-success-bg-subtle)',
|
||||
'--bs-accordion-btn-color': 'var(--bs-success-text-emphasis)',
|
||||
'--bs-accordion-active-bg': 'var(--bs-success)',
|
||||
'--bs-accordion-active-color': '#fff',
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.myId = counter++;
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-aggregators">
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_report_kpi_aggregators') }}</label>
|
||||
<div class="accordion mb-2">
|
||||
<div
|
||||
v-for="(agg, i) in modelValue"
|
||||
:key="i"
|
||||
class="accordion-item"
|
||||
>
|
||||
<h3
|
||||
class="accordion-header"
|
||||
:style="getHeaderStyle(defaultAggregatorIndex == i)"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="accordion-button collapsed"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#aggregator_' + myId + '_' + i"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'aggregator_' + myId + '_' + i"
|
||||
>
|
||||
<span v-if="agg.label">{{ agg.label }}</span>
|
||||
<i v-else>{{ $p.t('ui/neu') }}</i>
|
||||
</button>
|
||||
</h3>
|
||||
<div :id="'aggregator_' + myId + '_' + i" class="accordion-collapse collapse">
|
||||
<report-aggregator v-model="agg" class="accordion-body" :index="i" @remove="removeAggregator" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" @click="addAggregator">
|
||||
<i class="fa fa-plus" />
|
||||
{{ $p.t('dashboard/widget_report_kpi_aggregator') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import FormInput from '../../../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportAggregatorsAggregator",
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
inject: {
|
||||
defaultAggregatorIndex: 'defaultAggregatorIndex',
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
"remove",
|
||||
],
|
||||
methods: {
|
||||
remove() {
|
||||
this.$fhcAlert.confirmDelete()
|
||||
.then(result => {
|
||||
if (result)
|
||||
this.$emit('remove', this.index);
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-aggregators-aggregator">
|
||||
<form-input
|
||||
v-model="modelValue.label"
|
||||
type="text"
|
||||
:label="$p.t('global/label')"
|
||||
class="mb-3"
|
||||
/>
|
||||
<form-input
|
||||
v-model="modelValue.type"
|
||||
type="select"
|
||||
:label="$p.t('global/typ')"
|
||||
class="mb-3"
|
||||
>
|
||||
<option value="sum">{{ $p.t('dashboard/widget_report_kpi_aggregator_type_sum') }}</option>
|
||||
<option value="count">{{ $p.t('dashboard/widget_report_kpi_aggregator_type_count') }}</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="modelValue.type == 'sum'"
|
||||
v-model="modelValue.fields"
|
||||
type="text"
|
||||
:label="$p.t('dashboard/widget_report_kpi_aggregator_fields')"
|
||||
:placeholder="$p.t('dashboard/widget_report_kpi_aggregator_fields_placeholder')"
|
||||
class="mb-3"
|
||||
/>
|
||||
<div class="d-flex justify-content-between align-items-baseline">
|
||||
<form-input
|
||||
v-model="defaultAggregatorIndex"
|
||||
type="radio"
|
||||
name="aggregatordefault"
|
||||
container-class="form-switch"
|
||||
:label="$p.t('ui/default')"
|
||||
:value="index"
|
||||
/>
|
||||
<button type="button" class="btn btn-danger" @click="remove">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
{{ $p.t('ui/loeschen') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import FormInput from '../../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportAxis",
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
computed: {
|
||||
field: {
|
||||
get() {
|
||||
return this.modelValue?.field || '';
|
||||
},
|
||||
set(field) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, field });
|
||||
}
|
||||
},
|
||||
title: {
|
||||
get() {
|
||||
return this.modelValue?.title || '';
|
||||
},
|
||||
set(title) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, title });
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addChart() {
|
||||
this.modelValue.push({});
|
||||
},
|
||||
removeChart(index) {
|
||||
this.$emit('update:modelValue', this.modelValue.toSpliced(index, 1));
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-axis">
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_report_graph_axis') }}</label>
|
||||
<form-input
|
||||
v-model="field"
|
||||
:label="$p.t('dashboard/widget_report_graph_field')"
|
||||
/>
|
||||
<form-input
|
||||
v-model="title"
|
||||
:label="$p.t('global/label')"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import ReportChart from './Charts/Chart.js';
|
||||
|
||||
let counter = 0;
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportCharts",
|
||||
components: {
|
||||
ReportChart,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
myId: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
addChart() {
|
||||
this.modelValue.push({});
|
||||
},
|
||||
removeChart(index) {
|
||||
this.$emit('update:modelValue', this.modelValue.toSpliced(index, 1));
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.myId = counter++;
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-charts">
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_report_graph_charts') }}</label>
|
||||
<div class="accordion mb-2">
|
||||
<div
|
||||
v-for="(opt, i) in modelValue"
|
||||
:key="i"
|
||||
class="accordion-item"
|
||||
>
|
||||
<h3 class="accordion-header">
|
||||
<button
|
||||
type="button"
|
||||
class="accordion-button collapsed"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#chart_' + myId + '_' + i"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'chart_' + myId + '_' + i"
|
||||
>
|
||||
<span v-if="opt.label">{{ opt.label }}</span>
|
||||
<span v-else-if="opt.field">{{ opt.field }}</span>
|
||||
<i v-else>{{ $p.t('ui/neu') }}</i>
|
||||
</button>
|
||||
</h3>
|
||||
<div :id="'chart_' + myId + '_' + i" class="accordion-collapse collapse">
|
||||
<report-chart v-model="modelValue[i]" class="accordion-body" @remove="removeChart(i)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" @click="addChart">
|
||||
<i class="fa fa-plus" />
|
||||
{{ $p.t('dashboard/widget_report_graph_chart') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
import FormInput from '../../../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportChartsChart",
|
||||
components: {
|
||||
FormInput,
|
||||
PvDropdown: primevue.dropdown,
|
||||
PvColorpicker: primevue.colorpicker, // NOTE(chris): colorpicker must be loaded in view
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
"remove",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
typeOptions: [
|
||||
{
|
||||
value: 'bar',
|
||||
title: this.$p.t('dashboard/widget_report_graph_chart_type_bar'),
|
||||
optionHtml: /* html */`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 10"><rect x="0" y="5" width="8" height="5" fill="black"/><rect x="11" y="2" width="8" height="8" fill="black"/><rect x="22" y="6" width="8" height="4" fill="black"/></svg>`,
|
||||
},
|
||||
{
|
||||
value: 'line',
|
||||
title: this.$p.t('dashboard/widget_report_graph_chart_type_line'),
|
||||
optionHtml: /* html */`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 10"><path d="M0,5h30" stroke="black"/></svg>`,
|
||||
},
|
||||
{
|
||||
value: 'dashed',
|
||||
title: this.$p.t('dashboard/widget_report_graph_chart_type_dashed'),
|
||||
optionHtml: /* html */`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 10"><path d="M0,5h30" stroke="black" stroke-dasharray="4 4"/></svg>`,
|
||||
},
|
||||
{
|
||||
value: 'dotted',
|
||||
title: this.$p.t('dashboard/widget_report_graph_chart_type_dotted'),
|
||||
optionHtml: /* html */`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 10"><path d="M0,5h30" stroke="black" stroke-dasharray="0 4" stroke-linecap="round"/></svg>`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
field: {
|
||||
get() {
|
||||
return this.modelValue.field || '';
|
||||
},
|
||||
set(field) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, field });
|
||||
}
|
||||
},
|
||||
label: {
|
||||
get() {
|
||||
return this.modelValue.label || '';
|
||||
},
|
||||
set(label) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, label });
|
||||
}
|
||||
},
|
||||
color: {
|
||||
get() {
|
||||
return this.modelValue.color || '';
|
||||
},
|
||||
set(color) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, color });
|
||||
}
|
||||
},
|
||||
text: {
|
||||
get() {
|
||||
return this.modelValue.text || '';
|
||||
},
|
||||
set(text) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, text });
|
||||
}
|
||||
},
|
||||
type: {
|
||||
get() {
|
||||
if (!this.modelValue.type)
|
||||
return undefined;
|
||||
return this.typeOptions.find(opt => opt.value == this.modelValue.type);
|
||||
},
|
||||
set({ value: type }) {
|
||||
this.$emit('update:modelValue', { ...this.modelValue, type });
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
remove() {
|
||||
this.$fhcAlert.confirmDelete()
|
||||
.then(result => {
|
||||
if (result)
|
||||
this.$emit('remove');
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-charts-chart">
|
||||
<form-input
|
||||
v-model="field"
|
||||
:label="$p.t('dashboard/widget_report_graph_field')"
|
||||
class="mb-3"
|
||||
/>
|
||||
<form-input
|
||||
v-model="label"
|
||||
:label="$p.t('global/label')"
|
||||
class="mb-3"
|
||||
/>
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_report_graph_chart_layout') }}</label>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">{{ $p.t('global/typ') }}</span>
|
||||
<pv-dropdown
|
||||
v-model="type"
|
||||
:options="typeOptions"
|
||||
>
|
||||
<template #value="{ value, placeholder }">
|
||||
<div
|
||||
v-html="value ? value.optionHtml : placeholder"
|
||||
style="width:3rem"
|
||||
></div>
|
||||
</template>
|
||||
<template #option="{ option }">
|
||||
<div v-html="option.optionHtml"></div>
|
||||
</template>
|
||||
</pv-dropdown>
|
||||
<span
|
||||
class="input-group-text"
|
||||
:title="$p.t('dashboard/widget_report_graph_chart_bg_color')"
|
||||
>
|
||||
<i class="fa-solid fa-fill"></i>
|
||||
</span>
|
||||
<pv-colorpicker v-model="color" class="input-group-text"/>
|
||||
<form-input v-model="color" input-group></form-input>
|
||||
<span
|
||||
class="input-group-text"
|
||||
:title="$p.t('dashboard/widget_report_graph_chart_text_color')"
|
||||
>
|
||||
<i class="fa-solid fa-font"></i>
|
||||
</span>
|
||||
<pv-colorpicker v-model="text" class="input-group-text"/>
|
||||
<form-input v-model="text" input-group></form-input>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end align-items-baseline">
|
||||
<button type="button" class="btn btn-danger" @click="remove">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
{{ $p.t('ui/loeschen') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import FormInput from '../../../../Form/Input.js';
|
||||
import PickerOption from './Picker/Option.js';
|
||||
|
||||
import ApiReport from '../../../../../api/factory/report.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportPicker",
|
||||
components: {
|
||||
FormInput,
|
||||
PickerOption,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
allItems: null,
|
||||
filteredItems: [],
|
||||
selectedValue: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initSelectedValue() {
|
||||
if (this.modelValue) {
|
||||
const res = this.allItems.find(item => item.statistik_kurzbz == this.modelValue);
|
||||
|
||||
if (res)
|
||||
return this.selectedValue = res;
|
||||
|
||||
this.selectedValue = this.modelValue;
|
||||
} else {
|
||||
this.selectedValue = '';
|
||||
}
|
||||
},
|
||||
searchItems(event) {
|
||||
if (this.allItems) {
|
||||
if (!event.query) {
|
||||
this.filteredItems = this.allItems;
|
||||
} else {
|
||||
const regex = new RegExp(event.query, 'i');
|
||||
this.filteredItems = this.allItems.filter(t => regex.test(t.bezeichnung) || regex.test(t.statistik_kurzbz) || regex.test(t.gruppe));
|
||||
}
|
||||
}
|
||||
},
|
||||
updateSelectedItem(v) {
|
||||
if (v?.statistik_kurzbz) {
|
||||
this.$emit('update:modelValue', v.statistik_kurzbz);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$api
|
||||
.call(ApiReport.list())
|
||||
.then(result => {
|
||||
this.allItems = result.data;
|
||||
this.initSelectedValue();
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemErrors);
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-picker">
|
||||
<form-input
|
||||
type="autocomplete"
|
||||
:label="$p.t('dashboard/widget_report_statistik')"
|
||||
v-model="selectedValue"
|
||||
:suggestions="filteredItems"
|
||||
field="statistik_kurzbz"
|
||||
dropdown-mode="blank"
|
||||
dropdown
|
||||
force-selection
|
||||
@complete="searchItems"
|
||||
@update:modelValue="updateSelectedItem"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<picker-option :option="option" />
|
||||
</template>
|
||||
</form-input>
|
||||
<div v-if="selectedValue?.statistik_kurzbz" class="text-muted">
|
||||
{{ selectedValue.bezeichnung || selectedValue.statistik_kurzbz }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportPickerOption",
|
||||
props: {
|
||||
option: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-picker-option">
|
||||
<span>{{ option.bezeichnung || option.statistik_kurzbz }} </span>
|
||||
<small class="text-muted">
|
||||
[<b v-if="option.gruppe" class="text-muted">{{ option.gruppe }}: </b>
|
||||
{{ option.statistik_kurzbz }}]
|
||||
</small>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import ReportVar from './Vars/Var.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportVars",
|
||||
components: {
|
||||
ReportVar,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
details: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-vars">
|
||||
<label v-if="details.length" class="form-label">{{ $p.t('dashboard/widget_report_vars') }}</label>
|
||||
<template v-for="detail in details" :key="detail.kurzbz">
|
||||
<report-var
|
||||
:model-value="modelValue[detail.kurzbz]"
|
||||
:detail="detail"
|
||||
@update:model-value="$emit('update:modelValue', { ...modelValue, [detail.kurzbz]: $event })"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import FormInput from '../../../../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportVarsVarCalcDate",
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
computed: {
|
||||
modelValueCmp: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(v) {
|
||||
this.$emit('update:modelValue', v);
|
||||
},
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<form-input
|
||||
v-model="modelValueCmp"
|
||||
class="widgets-report-config-vars-var-calc-date"
|
||||
type="text"
|
||||
input-group
|
||||
:placeholder="$p.t('dashboard/widget_report_vars_calc_date_placeholder')"
|
||||
>
|
||||
</form-input>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import FormInput from '../../../../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportVarsVarCalcRight",
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
computed: {
|
||||
modelValueCmp: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(v) {
|
||||
this.$emit('update:modelValue', v);
|
||||
},
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<form-input
|
||||
v-model="modelValueCmp"
|
||||
class="widgets-report-config-vars-var-calc-right"
|
||||
type="text"
|
||||
input-group
|
||||
:placeholder="$p.t('dashboard/widget_report_vars_calc_right_placeholder')"
|
||||
>
|
||||
</form-input>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
import FormInput from '../../../../../Form/Input.js';
|
||||
|
||||
import { useCalculatedVars } from '../../../../../../composables/DashboardWidget/Report/CalculatedVars.js';
|
||||
|
||||
import { capitalize } from '../../../../../../helpers/StringHelpers.js';
|
||||
|
||||
import ApiReport from '../../../../../../api/factory/report.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGenteratorReportVarsVar",
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
detail: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
noType: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
computed: {
|
||||
fixedInputAttrs() {
|
||||
let attrs = {
|
||||
type: this.detail.type,
|
||||
};
|
||||
|
||||
if (this.detail.placeholder)
|
||||
attrs.placeholder = this.detail.placeholder;
|
||||
|
||||
if (this.detail.multiple)
|
||||
attrs.multiple = this.detail.multiple;
|
||||
|
||||
return attrs;
|
||||
},
|
||||
saveValue() {
|
||||
if (this.fixedInputAttrs.multiple)
|
||||
if (this.modelValue.value === undefined)
|
||||
return [];
|
||||
|
||||
return this.modelValue.value;
|
||||
},
|
||||
extendedType() {
|
||||
if (!this.modelValue?.type)
|
||||
return [];
|
||||
return this.modelValue.type.split(':');
|
||||
},
|
||||
calcTypeExtra: {
|
||||
get() {
|
||||
if (this.extendedType.length > 1)
|
||||
return this.extendedType[2];
|
||||
return undefined;
|
||||
},
|
||||
set(v) {
|
||||
this.setType([this.extendedType[0], this.extendedType[1], v].join(':'));
|
||||
},
|
||||
},
|
||||
calcComponent() {
|
||||
if (this.noType)
|
||||
return null;
|
||||
if (this.extendedType.length < 2)
|
||||
return null;
|
||||
if (this.extendedType[0] != 'calc')
|
||||
return null;
|
||||
if (!this.calcComponents[this.extendedType[1]])
|
||||
return null;
|
||||
|
||||
return this.calcComponents[this.extendedType[1]];
|
||||
},
|
||||
options() {
|
||||
if (this.extendedType.length < 1 && !this.noType)
|
||||
return [];
|
||||
if (this.extendedType[0] == 'calc' && !this.noType)
|
||||
return this.calcOptions;
|
||||
if (this.detail.type == 'select')
|
||||
return this.detail.options;
|
||||
return [];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getComponent(string) {
|
||||
return 'Report' + capitalize(string);
|
||||
},
|
||||
setType(type) {
|
||||
if (type == 'user' || type.substr(0, 5) == 'calc:') {
|
||||
this.$emit('update:modelValue', { type, detail: this.detail });
|
||||
} else {
|
||||
this.$emit('update:modelValue', { type });
|
||||
}
|
||||
},
|
||||
setSubType(subtype) {
|
||||
let type = 'calc:' + subtype;
|
||||
this.$emit('update:modelValue', { type, detail: this.detail });
|
||||
},
|
||||
setValue(value) {
|
||||
this.modelValue.value = value;
|
||||
this.$emit('update:modelValue', this.modelValue);
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { components, options } = useCalculatedVars();
|
||||
return {
|
||||
calcComponents: components,
|
||||
calcOptions: options,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (!this.modelValue?.type) {
|
||||
this.setType('fix');
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-vars-var input-group">
|
||||
<span class="input-group-text">
|
||||
{{ detail.title }}
|
||||
</span>
|
||||
<form-input
|
||||
v-if="!noType"
|
||||
type="select"
|
||||
:modelValue="extendedType[0]"
|
||||
container-class="d-flex"
|
||||
input-group
|
||||
@update:modelValue="setType"
|
||||
>
|
||||
<option value="fix">{{ $p.t('dashboard/widget_report_vars_var_type_fix') }}</option>
|
||||
<option value="calc">{{ $p.t('dashboard/widget_report_vars_var_type_calc') }}</option>
|
||||
<option value="user">{{ $p.t('dashboard/widget_report_vars_var_type_user') }}</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="modelValue.type == 'fix' || noType"
|
||||
:modelValue="saveValue"
|
||||
v-bind="fixedInputAttrs"
|
||||
input-group
|
||||
@update:modelValue="setValue"
|
||||
>
|
||||
<option v-for="option in options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="extendedType[0] == 'calc' && !noType"
|
||||
:modelValue="extendedType[1]"
|
||||
type="select"
|
||||
input-group
|
||||
@update:modelValue="setSubType"
|
||||
>
|
||||
<option v-for="option in options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</form-input>
|
||||
<component v-if="calcComponent" :is="calcComponent" v-model="calcTypeExtra" />
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import FormInput from "../../../Form/Input.js";
|
||||
import EditSize from "./Size.js";
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAdminEditSetup',
|
||||
components: {
|
||||
FormInput,
|
||||
EditSize,
|
||||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
editName: Boolean,
|
||||
editSize: Boolean,
|
||||
editHideFooter: Boolean,
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue"
|
||||
],
|
||||
template: /* html */`
|
||||
<div class="widgets-admin-edit-setup">
|
||||
<form-input
|
||||
v-model="modelValue.name"
|
||||
type="text"
|
||||
container-class="mb-3"
|
||||
:label="$p.t('global/name')"
|
||||
:disabled="!editName"
|
||||
/>
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_setup_size') }}</label>
|
||||
<div class="d-flex gap-3 mb-3">
|
||||
<edit-size v-model="modelValue.width" :disabled="!editSize">
|
||||
{{ $p.t('dashboard/widget_setup_width') }}
|
||||
</edit-size>
|
||||
<edit-size v-model="modelValue.height" :disabled="!editSize">
|
||||
{{ $p.t('dashboard/widget_setup_height') }}
|
||||
</edit-size>
|
||||
</div>
|
||||
<form-input
|
||||
v-model="modelValue.hideFooter"
|
||||
type="checkbox"
|
||||
container-class="form-switch mb-3"
|
||||
role="switch"
|
||||
:label="$p.t('dashboard/widget_setup_hidefooter')"
|
||||
:disabled="!editHideFooter"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import FormInput from "../../../Form/Input.js";
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAdminEditSize',
|
||||
components: {
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: [ Object, Number ],
|
||||
disabled: Boolean,
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue"
|
||||
],
|
||||
computed: {
|
||||
min: {
|
||||
get() {
|
||||
if (Number.isInteger(this.modelValue))
|
||||
return this.modelValue;
|
||||
if (this.modelValue?.min === undefined)
|
||||
return '';
|
||||
return this.modelValue.min;
|
||||
},
|
||||
set(v) {
|
||||
if (v === '') {
|
||||
if (this.max === '')
|
||||
this.$emit('update:modelValue', undefined);
|
||||
else
|
||||
this.$emit('update:modelValue', { max: this.max });
|
||||
} else if (v == this.max) {
|
||||
this.$emit('update:modelValue', v);
|
||||
} else {
|
||||
if (this.max === '')
|
||||
this.$emit('update:modelValue', { min: v });
|
||||
else
|
||||
this.$emit('update:modelValue', {
|
||||
min: v,
|
||||
max: this.max,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
max: {
|
||||
get() {
|
||||
if (Number.isInteger(this.modelValue))
|
||||
return this.modelValue;
|
||||
if (this.modelValue?.max === undefined)
|
||||
return '';
|
||||
return this.modelValue.max;
|
||||
},
|
||||
set(v) {
|
||||
if (v === '') {
|
||||
if (this.min === '')
|
||||
this.$emit('update:modelValue', undefined);
|
||||
else
|
||||
this.$emit('update:modelValue', { min: this.min });
|
||||
} else if (v == this.min) {
|
||||
this.$emit('update:modelValue', v);
|
||||
} else {
|
||||
if (this.min === '')
|
||||
this.$emit('update:modelValue', { max: v });
|
||||
else
|
||||
this.$emit('update:modelValue', {
|
||||
min: this.min,
|
||||
max: v,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="widgets-admin-edit-size input-group">
|
||||
<span class="input-group-text"><slot /></span>
|
||||
<form-input
|
||||
v-model="min"
|
||||
type="text"
|
||||
placeholder="1"
|
||||
:disabled="disabled"
|
||||
input-group
|
||||
/>
|
||||
<span class="input-group-text">-</span>
|
||||
<form-input
|
||||
v-model="max"
|
||||
type="text"
|
||||
placeholder="∞"
|
||||
:disabled="disabled"
|
||||
input-group
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import { CoreFilterCmpt } from "../../filter/Filter.js";
|
||||
import BsConfirm from "../../Bootstrap/Confirm.js";
|
||||
|
||||
import ApiDashboardWidget from "../../../api/factory/dashboard/widget.js";
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAdminList',
|
||||
components: {
|
||||
CoreFilterCmpt,
|
||||
},
|
||||
inject: {
|
||||
imageSrc: "imageSrc",
|
||||
},
|
||||
props: {
|
||||
unsavedProgress: Boolean,
|
||||
},
|
||||
emit: [
|
||||
"new",
|
||||
"select",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
tabulatorOptions: {
|
||||
ajaxURL: 'dummy',
|
||||
ajaxRequestFunc: () => this.$api.call(ApiDashboardWidget.listAllOriginal()),
|
||||
ajaxResponse: (url, params, response) => response?.data,
|
||||
columns: [
|
||||
{
|
||||
field: 'setup.icon',
|
||||
title: 'Icon',
|
||||
titlePhrase: 'dashboard/widget_icon',
|
||||
formatter: (cell, formatterParams) => {
|
||||
const img = document.createElement('img');
|
||||
img.src = this.imageSrc(cell.getData().setup?.icon);
|
||||
|
||||
if (formatterParams?.height)
|
||||
img.style.height = formatterParams.height;
|
||||
|
||||
return img;
|
||||
},
|
||||
formatterParams: {
|
||||
height: '1.5em',
|
||||
},
|
||||
hozAlign: 'center'
|
||||
},
|
||||
{
|
||||
field: 'setup.name',
|
||||
title: 'Name',
|
||||
titlePhrase: 'global/name',
|
||||
},
|
||||
{
|
||||
field: 'widget_kurzbz',
|
||||
title: 'Kurzbz',
|
||||
titlePhrase: 'dashboard/widget_kurzbz',
|
||||
},
|
||||
{
|
||||
field: 'berechtigung_kurzbz',
|
||||
title: 'Berechtigung',
|
||||
titlePhrase: 'global/permission',
|
||||
},
|
||||
],
|
||||
locale: true,
|
||||
selectableRows: 1,
|
||||
index: 'widget_id',
|
||||
persistenceID: 'widgets-admin-list',
|
||||
height: '100%',
|
||||
selectableRowsCheck: this.selectableCheck,
|
||||
},
|
||||
tabulatorEvents: [
|
||||
{
|
||||
event: 'rowSelected',
|
||||
handler: this.selectWidget,
|
||||
},
|
||||
{
|
||||
event: 'dataLoading',
|
||||
handler: () => {
|
||||
// save current
|
||||
const selected = this.$refs.table.tabulator.getSelectedRows();
|
||||
if (selected.length)
|
||||
this.lastSelected = selected[0].getData().widget_id;
|
||||
},
|
||||
},
|
||||
{
|
||||
event: 'dataProcessing',
|
||||
handler: () => {
|
||||
this.adding = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
event: 'dataProcessed',
|
||||
handler: () => {
|
||||
// reselect current
|
||||
this.adding = false;
|
||||
if (this.lastSelected !== null) {
|
||||
this.$refs.table.tabulator.selectRow(this.lastSelected);
|
||||
this.lastSelected = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
lastSelected: null,
|
||||
adding: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
selectRow(row) {
|
||||
// deselect
|
||||
row.getTable().getSelectedRows().forEach(r => r.deselect());
|
||||
// select
|
||||
row.select();
|
||||
},
|
||||
updateOrAddDataAndSelect(data) {
|
||||
this.$refs.table.tabulator
|
||||
.updateOrAddData([ data ])
|
||||
.then(res => this.selectRow(res[0]))
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
},
|
||||
selectableCheck(row) {
|
||||
if (this.adding)
|
||||
return true;
|
||||
if (!this.unsavedProgress)
|
||||
return true;
|
||||
|
||||
BsConfirm
|
||||
.popup(this.$p.t('dashboard/confirm_unsaved_progress'))
|
||||
.then(() => this.selectRow(row))
|
||||
.catch(() => {});
|
||||
|
||||
return false;
|
||||
},
|
||||
newWidget() {
|
||||
if (this.unsavedProgress) {
|
||||
BsConfirm
|
||||
.popup(this.$p.t('dashboard/confirm_unsaved_progress'))
|
||||
.then(() => this.$emit('new'))
|
||||
.catch(() => {});
|
||||
} else {
|
||||
this.$emit('new');
|
||||
}
|
||||
},
|
||||
selectWidget(data) {
|
||||
if (this.lastSelected === null)
|
||||
this.$emit('select', data.getData());
|
||||
|
||||
if (!data.isSelected()) {
|
||||
/** NOTE(chris): There is a bug in Tabulator where the selected
|
||||
* object gets duplicated and doesnt't match in comparisons.
|
||||
* This results in two visible selected entries, but only one of
|
||||
* them is really selected. This seems to happen if a newly
|
||||
* added row is selected the second time.
|
||||
*/
|
||||
this.selectRow(data);
|
||||
}
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="widgets-admin-list h-100 d-flex flex-column">
|
||||
<core-filter-cmpt
|
||||
ref="table"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
:tabulator-events="tabulatorEvents"
|
||||
:side-menu="false"
|
||||
table-only
|
||||
reload
|
||||
new-btn-show
|
||||
:new-btn-label="$p.t('dashboard/widget')"
|
||||
@click:new="newWidget"
|
||||
>
|
||||
</core-filter-cmpt>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import BsModal from '../../Bootstrap/Modal.js';
|
||||
import CoreForm from '../../Form/Form.js';
|
||||
import FormInput from '../../Form/Input.js';
|
||||
|
||||
import ApiWidget from '../../../api/factory/dashboard/widget.js';
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAdminNew',
|
||||
components: {
|
||||
BsModal,
|
||||
CoreForm,
|
||||
FormInput,
|
||||
},
|
||||
emit: [
|
||||
"create",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
generators: [],
|
||||
result: { setup: {} },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.result = {
|
||||
widget_kurzbz: '',
|
||||
setup: {
|
||||
generator: null,
|
||||
},
|
||||
};
|
||||
this.$refs.form.clearValidation();
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
finish() {
|
||||
this.$refs.form
|
||||
.call(ApiWidget.create(this.result))
|
||||
.then(res => {
|
||||
this.$emit('create', res.data);
|
||||
this.$refs.modal.hide();
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$api
|
||||
.call(ApiWidget.generators())
|
||||
.then(res => {
|
||||
this.generators = res.data
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
template: /* html */`
|
||||
<core-form
|
||||
class="widgets-admin-new"
|
||||
ref="form"
|
||||
@submit.prevent="finish"
|
||||
>
|
||||
<bs-modal ref="modal">
|
||||
<template #default>
|
||||
<form-input
|
||||
v-model="result.setup.generator"
|
||||
type="select"
|
||||
:label="$p.t('dashboard/generator')"
|
||||
class="mb-3"
|
||||
>
|
||||
<option v-for="(generator, name) in generators" :key="name" :value="generator">
|
||||
{{ generator }}
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-model="result.widget_kurzbz"
|
||||
type="text"
|
||||
name="widget_kurzbz"
|
||||
:label="$p.t('dashboard/widget_kurzbz') + ':'"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ $p.t('ui/ok') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
{{ $p.t('ui/cancel') }}
|
||||
</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
</core-form>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import EditSetup from '../../../Dashboard/WidgetAdmin/Edit/Setup.js';
|
||||
import EditPermission from '../../../Dashboard/WidgetAdmin/Edit/Permission.js';
|
||||
import ReportPicker from '../../../Dashboard/WidgetAdmin/Edit/Report/Picker.js';
|
||||
import ReportVars from '../../../Dashboard/WidgetAdmin/Edit/Report/Vars.js';
|
||||
import ReportAxis from '../../../Dashboard/WidgetAdmin/Edit/Report/Axis.js';
|
||||
import ReportCharts from '../../../Dashboard/WidgetAdmin/Edit/Report/Charts.js';
|
||||
import FormInput from '../../../Form/Input.js';
|
||||
import BsConfirm from "../../../Bootstrap/Confirm.js";
|
||||
|
||||
import ApiReport from '../../../../api/factory/report.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGeneratorReportGraph",
|
||||
components: {
|
||||
EditSetup,
|
||||
EditPermission,
|
||||
ReportPicker,
|
||||
ReportVars,
|
||||
ReportAxis,
|
||||
ReportCharts,
|
||||
FormInput,
|
||||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
details: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getTitleFromPicker() {
|
||||
const statistik_kurzbz = this.modelValue.arguments.statistik_kurzbz;
|
||||
|
||||
let currentItem = this.$refs.picker.allItems
|
||||
.find(item => item.statistik_kurzbz == statistik_kurzbz);
|
||||
|
||||
return currentItem?.bezeichnung || statistik_kurzbz;
|
||||
},
|
||||
attemptReportChange(value) {
|
||||
if (this.modelValue.arguments.statistik_kurzbz == value)
|
||||
return;
|
||||
|
||||
const hasVars = Object.entries(this.modelValue.arguments.vars).length;
|
||||
const hasGraphoptions = this.modelValue.arguments.charts.length;
|
||||
const hasDefaultTitle = this.modelValue.arguments.title == this.getTitleFromPicker();
|
||||
|
||||
if (hasVars || hasGraphoptions) {
|
||||
BsConfirm
|
||||
.popup(this.$p.t('dashboard/widget_report_statistik_change'))
|
||||
.then(() => {
|
||||
this.modelValue.arguments.statistik_kurzbz = value;
|
||||
this.modelValue.arguments.charts = [];
|
||||
if (hasDefaultTitle || this.modelValue.arguments.title == '')
|
||||
this.modelValue.arguments.title = this.getTitleFromPicker();
|
||||
this.loadDetails();
|
||||
})
|
||||
.catch(() => {
|
||||
this.$refs.picker.initSelectedValue();
|
||||
});
|
||||
} else {
|
||||
this.modelValue.arguments.statistik_kurzbz = value;
|
||||
if (hasDefaultTitle || this.modelValue.arguments.title == '')
|
||||
this.modelValue.arguments.title = this.getTitleFromPicker();
|
||||
this.loadDetails();
|
||||
}
|
||||
},
|
||||
loadDetails() {
|
||||
this.details = null;
|
||||
this.$api
|
||||
.call(ApiReport.vars(this.modelValue.arguments.statistik_kurzbz))
|
||||
.then(result => {
|
||||
const allowedKeys = result.data.map(detail => detail.kurzbz);
|
||||
if (!this.modelValue.arguments.vars)
|
||||
this.modelValue.arguments.vars = {};
|
||||
else
|
||||
for (var key in this.modelValue.arguments.vars) {
|
||||
if (!allowedKeys.includes(key))
|
||||
delete this.modelValue.arguments.vars[key];
|
||||
};
|
||||
result.data.forEach(detail => {
|
||||
if (!this.modelValue.arguments.vars[detail.kurzbz])
|
||||
this.modelValue.arguments.vars[detail.kurzbz] = { type: 'fix' };
|
||||
});
|
||||
|
||||
if (result.meta?.berechtigung_kurzbz)
|
||||
this.modelValue.berechtigung_kurzbz = result.meta.berechtigung_kurzbz;
|
||||
|
||||
this.details = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemErrors)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.modelValue.arguments.statistik_kurzbz)
|
||||
this.loadDetails(this.modelValue.arguments.statistik_kurzbz);
|
||||
|
||||
if (!this.modelValue.arguments.vars) {
|
||||
this.$emit('update:modelValue', {
|
||||
...this.modelValue,
|
||||
setup: {
|
||||
...this.modelValue.setup,
|
||||
icon: '/public/images/widgets/Graph_Widget.svg',
|
||||
name: '',
|
||||
hideFooter: false,
|
||||
},
|
||||
arguments: {
|
||||
title: '',
|
||||
statistik_kurzbz: '',
|
||||
vars: {},
|
||||
xAxis: {
|
||||
field: '',
|
||||
title: '',
|
||||
},
|
||||
charts: [],
|
||||
},
|
||||
berechtigung_kurzbz: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-generator-report-graph">
|
||||
<edit-setup
|
||||
v-model="modelValue.setup"
|
||||
class="border-bottom mb-3"
|
||||
edit-name
|
||||
edit-size
|
||||
edit-hide-footer
|
||||
/>
|
||||
<form-input
|
||||
v-model="modelValue.arguments.title"
|
||||
:label="$p.t('global/titel')"
|
||||
class="mb-3"
|
||||
/>
|
||||
<report-picker
|
||||
ref="picker"
|
||||
:model-value="modelValue.arguments.statistik_kurzbz || ''"
|
||||
class="mb-3"
|
||||
@update:model-value="attemptReportChange"
|
||||
/>
|
||||
<template v-if="details">
|
||||
<report-vars
|
||||
v-model="modelValue.arguments.vars"
|
||||
:details="details"
|
||||
class="mb-3"
|
||||
/>
|
||||
<report-axis
|
||||
v-model="modelValue.arguments.xAxis"
|
||||
class="mb-3"
|
||||
/>
|
||||
<report-charts
|
||||
v-model="modelValue.arguments.charts"
|
||||
class="mb-3"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="modelValue?.arguments?.statistik_kurzbz"
|
||||
class="placeholder-glow mb-3"
|
||||
>
|
||||
<span class="placeholder col-6"></span>
|
||||
</div>
|
||||
<edit-permission v-model="modelValue.berechtigung_kurzbz" disabled />
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
import EditSetup from '../../../Dashboard/WidgetAdmin/Edit/Setup.js';
|
||||
import EditPermission from '../../../Dashboard/WidgetAdmin/Edit/Permission.js';
|
||||
import ReportPicker from '../../../Dashboard/WidgetAdmin/Edit/Report/Picker.js';
|
||||
import ReportVars from '../../../Dashboard/WidgetAdmin/Edit/Report/Vars.js';
|
||||
import ReportAggregators from '../../../Dashboard/WidgetAdmin/Edit/Report/Aggregators.js';
|
||||
import BsConfirm from "../../../Bootstrap/Confirm.js";
|
||||
|
||||
import ApiReport from '../../../../api/factory/report.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsGeneratorReportKpi",
|
||||
components: {
|
||||
EditSetup,
|
||||
EditPermission,
|
||||
ReportPicker,
|
||||
ReportVars,
|
||||
ReportAggregators,
|
||||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
},
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
details: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
attemptReportChange(value) {
|
||||
if (this.modelValue.arguments.statistik_kurzbz == value)
|
||||
return;
|
||||
|
||||
const hasVars = Object.entries(this.modelValue.arguments.vars).length;
|
||||
const hasAggregators = this.modelValue.arguments.aggregators.length;
|
||||
|
||||
if (hasVars || hasAggregators) {
|
||||
BsConfirm
|
||||
.popup(this.$p.t('dashboard/widget_report_statistik_change'))
|
||||
.then(() => {
|
||||
this.modelValue.arguments.statistik_kurzbz = value;
|
||||
this.modelValue.arguments.aggregators = [];
|
||||
this.loadDetails();
|
||||
})
|
||||
.catch(() => {
|
||||
this.$refs.picker.initSelectedValue();
|
||||
});
|
||||
} else {
|
||||
this.modelValue.arguments.statistik_kurzbz = value;
|
||||
this.loadDetails();
|
||||
}
|
||||
},
|
||||
loadDetails() {
|
||||
this.details = null;
|
||||
this.$api
|
||||
.call(ApiReport.vars(this.modelValue.arguments.statistik_kurzbz))
|
||||
.then(result => {
|
||||
const allowedKeys = result.data.map(detail => detail.kurzbz);
|
||||
if (!this.modelValue.arguments.vars)
|
||||
this.modelValue.arguments.vars = {};
|
||||
else
|
||||
for (var key in this.modelValue.arguments.vars) {
|
||||
if (!allowedKeys.includes(key))
|
||||
delete this.modelValue.arguments.vars[key];
|
||||
};
|
||||
result.data.forEach(detail => {
|
||||
if (!this.modelValue.arguments.vars[detail.kurzbz])
|
||||
this.modelValue.arguments.vars[detail.kurzbz] = { type: 'fix' };
|
||||
});
|
||||
|
||||
if (result.meta?.berechtigung_kurzbz)
|
||||
this.modelValue.berechtigung_kurzbz = result.meta.berechtigung_kurzbz;
|
||||
|
||||
this.details = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemErrors)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.modelValue.arguments.statistik_kurzbz)
|
||||
this.loadDetails(this.modelValue.arguments.statistik_kurzbz);
|
||||
|
||||
if (!this.modelValue.arguments.vars) {
|
||||
this.$emit('update:modelValue', {
|
||||
...this.modelValue,
|
||||
setup: {
|
||||
...this.modelValue.setup,
|
||||
icon: '/public/images/widgets/KPI_Widget.svg',
|
||||
name: '',
|
||||
width: 1,
|
||||
height: 1,
|
||||
hideFooter: false,
|
||||
},
|
||||
arguments: {
|
||||
statistik_kurzbz: '',
|
||||
vars: {},
|
||||
aggregators: [],
|
||||
},
|
||||
berechtigung_kurzbz: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-generator-report-kpi">
|
||||
<edit-setup
|
||||
v-model="modelValue.setup"
|
||||
class="border-bottom mb-3"
|
||||
edit-name
|
||||
/>
|
||||
<report-picker
|
||||
ref="picker"
|
||||
:model-value="modelValue.arguments.statistik_kurzbz || ''"
|
||||
class="mb-3"
|
||||
@update:model-value="attemptReportChange"
|
||||
/>
|
||||
<template v-if="details">
|
||||
<report-vars
|
||||
v-model="modelValue.arguments.vars"
|
||||
:details="details"
|
||||
class="mb-3"
|
||||
/>
|
||||
<report-aggregators
|
||||
v-model="modelValue.arguments.aggregators"
|
||||
:details="details"
|
||||
class="mb-3"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="modelValue?.arguments?.statistik_kurzbz"
|
||||
class="placeholder-glow mb-3"
|
||||
>
|
||||
<span class="placeholder col-6"></span>
|
||||
</div>
|
||||
<edit-permission v-model="modelValue.berechtigung_kurzbz" disabled />
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import AbstractWidget from '../../Abstract.js';
|
||||
import VarsVar from '../../../Dashboard/WidgetAdmin/Edit/Report/Vars/Var.js';
|
||||
import FormInput from '../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsReportGraphSetup",
|
||||
components:{
|
||||
VarsVar,
|
||||
FormInput,
|
||||
},
|
||||
mixins: [ AbstractWidget ],
|
||||
computed: {
|
||||
filter: {
|
||||
get() {
|
||||
if (this.config.filter !== undefined)
|
||||
return this.config.filter;
|
||||
|
||||
if (this.config.charts.length > 1)
|
||||
return Array.from(this.config.charts.keys());
|
||||
|
||||
return [];
|
||||
},
|
||||
set(v) {
|
||||
if (v.length == this.config.charts.length)
|
||||
v = undefined;
|
||||
this.config.filter = v;
|
||||
}
|
||||
},
|
||||
customVars() {
|
||||
const uservars = this.config.uservars || {};
|
||||
|
||||
return Object.entries(this.config.vars).reduce((res, [key, variable]) => {
|
||||
if (variable.type == 'user') {
|
||||
res[key] = {
|
||||
modelValue: uservars[key] || {},
|
||||
detail: variable.detail,
|
||||
};
|
||||
} else if (this.sharedData && this.sharedData[key]) {
|
||||
res[key] = {
|
||||
modelValue: uservars[key] || {},
|
||||
detail: this.sharedData[key],
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}, {});
|
||||
},
|
||||
hasCustomVars() {
|
||||
return Object.keys(this.customVars).length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateUserVar(key, { value }) {
|
||||
if (!this.config.uservars)
|
||||
this.config.uservars = {};
|
||||
this.config.uservars[key] = { value };
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-graph">
|
||||
<div v-if="hasCustomVars" :class="config.charts.length > 1 ? 'mb-3' : ''">
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_report_vars') }}</label>
|
||||
<vars-var
|
||||
v-for="(variable, key) in customVars"
|
||||
:key="key"
|
||||
v-bind="variable"
|
||||
no-type
|
||||
@update:model-value="updateUserVar(key, $event)"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="config.charts.length > 1">
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_report_graph_charts') }}</label>
|
||||
<div v-for="(chart, i) in config.charts">
|
||||
<form-input
|
||||
v-model="filter"
|
||||
type="checkbox"
|
||||
:label="chart.label || chart.field"
|
||||
name="filter"
|
||||
:value="i"
|
||||
:disabled="filter.length == 1 && filter.indexOf(i) != -1"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import AbstractWidget from '../../Abstract.js';
|
||||
import VarsVar from '../../../Dashboard/WidgetAdmin/Edit/Report/Vars/Var.js';
|
||||
import FormInput from '../../../Form/Input.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsReportKpiSetup",
|
||||
components:{
|
||||
VarsVar,
|
||||
FormInput,
|
||||
},
|
||||
mixins: [ AbstractWidget ],
|
||||
computed: {
|
||||
aggregator: {
|
||||
get() {
|
||||
if (this.config.aggregator !== undefined)
|
||||
return this.config.aggregator;
|
||||
|
||||
if (this.config.aggregators.length == 1)
|
||||
return this.config.aggregators[0];
|
||||
|
||||
if (this.config.aggregators.length > 1) {
|
||||
const def = this.config.aggregators.findIndex(agg => agg.default);
|
||||
if (def > 0)
|
||||
return def;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
set(v) {
|
||||
this.config.aggregator = v;
|
||||
}
|
||||
},
|
||||
customVars() {
|
||||
const uservars = this.config.uservars || {};
|
||||
|
||||
return Object.entries(this.config.vars).reduce((res, [key, variable]) => {
|
||||
if (variable.type == 'user') {
|
||||
res[key] = {
|
||||
modelValue: uservars[key] || {},
|
||||
detail: variable.detail,
|
||||
};
|
||||
} else if (this.sharedData && this.sharedData[key]) {
|
||||
res[key] = {
|
||||
modelValue: uservars[key] || {},
|
||||
detail: this.sharedData[key],
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}, {});
|
||||
},
|
||||
hasCustomVars() {
|
||||
return Object.keys(this.customVars).length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateUserVar(key, { value }) {
|
||||
if (!this.config.uservars)
|
||||
this.config.uservars = {};
|
||||
this.config.uservars[key] = { value };
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-config-kpi">
|
||||
<div v-if="hasCustomVars" :class="config.aggregators.length > 1 ? 'mb-3' : ''">
|
||||
<label class="form-label">{{ $p.t('dashboard/widget_report_vars') }}</label>
|
||||
<vars-var
|
||||
v-for="(variable, key) in customVars"
|
||||
:key="key"
|
||||
v-bind="variable"
|
||||
no-type
|
||||
@update:model-value="updateUserVar(key, $event)"
|
||||
/>
|
||||
</div>
|
||||
<form-input
|
||||
v-if="config.aggregators.length > 1"
|
||||
type="select"
|
||||
:label="$p.t('dashboard/widget_report_kpi_aggregator')"
|
||||
v-model="aggregator"
|
||||
>
|
||||
<option v-for="(aggregator, i) in config.aggregators" :key="i" :value="i">
|
||||
{{ aggregator.label }}
|
||||
</option>
|
||||
</form-input>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
export default {
|
||||
name: "WidgetsReportError",
|
||||
props: {
|
||||
errors: {
|
||||
type: [ Object, Boolean ],
|
||||
required: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
myClass() {
|
||||
if (this.errors !== true)
|
||||
return 'w-100 overflow-scroll';
|
||||
|
||||
return [
|
||||
'alert',
|
||||
'alert-danger',
|
||||
'm-0',
|
||||
'h-100',
|
||||
'w-100',
|
||||
'border-0',
|
||||
'rounded-0',
|
||||
'd-flex',
|
||||
'justify-content-center',
|
||||
'align-items-center'
|
||||
];
|
||||
},
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-error" :class="myClass">
|
||||
<template v-if="errors === true">
|
||||
{{ $p.t('ui/errorConfigFehlt') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-for="error in errors" :key="error">
|
||||
<div v-if="error.message" class="alert alert-danger m-1">
|
||||
{{ error.message }}
|
||||
</div>
|
||||
<template v-else-if="error.messages">
|
||||
<div v-for="msg in error.messages" :key="msg" class="alert alert-danger m-1">
|
||||
{{ msg }}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,253 @@
|
||||
import AbstractWidget from '../Abstract.js';
|
||||
import ReportError from './Error/Error.js';
|
||||
import ConfigGraph from './Config/Graph.js';
|
||||
import FhcChart from '../../Chart/FhcChart.js';
|
||||
|
||||
import { useCalculatedVars } from '../../../composables/DashboardWidget/Report/CalculatedVars.js';
|
||||
|
||||
import ApiReport from '../../../api/factory/report.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsReportGraph",
|
||||
components: {
|
||||
ReportError,
|
||||
ConfigGraph,
|
||||
FhcChart,
|
||||
},
|
||||
mixins: [ AbstractWidget ],
|
||||
inject: {
|
||||
adminMode: {
|
||||
from: 'adminMode',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
calledVars: {},
|
||||
data: undefined,
|
||||
hasErrors: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredCharts() {
|
||||
if (!this.config.filter)
|
||||
return this.config.charts;
|
||||
return this.config.charts.filter((_data, index) => this.config.filter.includes(index));
|
||||
},
|
||||
filteredData() {
|
||||
if (!this.data)
|
||||
return null;
|
||||
|
||||
const title = (this.config.title || '').replace(
|
||||
/\{\s*([^\}]+)\s*\}/g,
|
||||
(match, key) => {
|
||||
if (this.calledVars[key] === undefined)
|
||||
return match;
|
||||
if (this.config.vars[key].detail?.options) {
|
||||
const options = this.config.vars[key].detail.options;
|
||||
const res = options.find(opt => opt.value == this.calledVars[key]);
|
||||
if (res?.label)
|
||||
return res.label;
|
||||
}
|
||||
return this.calledVars[key];
|
||||
}
|
||||
);
|
||||
|
||||
let result = {
|
||||
title: {
|
||||
text: title,
|
||||
},
|
||||
credits: {
|
||||
text: "",
|
||||
href: "https:\/\/fhcomplete.org"
|
||||
},
|
||||
chart: {
|
||||
zoomType: "xy",
|
||||
type: "line",
|
||||
animation: true
|
||||
},
|
||||
xAxis: {
|
||||
categories: [],
|
||||
title: {
|
||||
text: this.config.xAxis.title || '',
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
title: { text: "" },
|
||||
},
|
||||
series: this.filteredCharts.reduce((res, curr) => {
|
||||
res[curr.field] = {
|
||||
name: curr.label || curr.field,
|
||||
data: [],
|
||||
};
|
||||
switch (curr.type) {
|
||||
case "bar":
|
||||
res[curr.field].type = "column";
|
||||
break;
|
||||
case "dotted":
|
||||
res[curr.field].dashStyle = "dot";
|
||||
break;
|
||||
case "dashed":
|
||||
res[curr.field].dashStyle = "longdash";
|
||||
break;
|
||||
}
|
||||
if (curr.color)
|
||||
res[curr.field].color = '#' + curr.color.replace(/^#+/, '');
|
||||
if (curr.text)
|
||||
res[curr.field].dataLabels = { color: '#' + curr.text.replace(/^#+/, '') };
|
||||
return res;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
const chartNames = Object.keys(result.series);
|
||||
|
||||
result = this.data.reduce((res, curr) => {
|
||||
const xValue = curr[this.config.xAxis.field];
|
||||
res.xAxis.categories.push(xValue);
|
||||
chartNames.forEach(
|
||||
chart => res.series[chart].data.push([xValue, Number(curr[chart])])
|
||||
);
|
||||
return res;
|
||||
}, result);
|
||||
|
||||
result.series = Object.values(result.series);
|
||||
|
||||
return result;
|
||||
},
|
||||
chartSetup() {
|
||||
if (this.adminMode)
|
||||
return '?';
|
||||
if (this.filteredData !== null)
|
||||
return this.filteredData;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config: {
|
||||
handler() { this.fetchData(); },
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
width(o, n) {
|
||||
if (o != n && this.$refs.chart)
|
||||
this.$refs.chart.reflow();
|
||||
},
|
||||
height(o, n) {
|
||||
if (o != n && this.$refs.chart)
|
||||
this.$refs.chart.reflow();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
if (this.configMode)
|
||||
return;
|
||||
|
||||
this.hasErrors = false;
|
||||
|
||||
let vars = {};
|
||||
for (var key in this.config.vars) {
|
||||
vars[key] = await this.getValueForVar(this.config.vars[key]);
|
||||
|
||||
if (this.config.vars[key].type == 'user') {
|
||||
if (!this.config.uservars || !this.config.uservars[key])
|
||||
delete vars[key];
|
||||
else
|
||||
vars[key] = this.config.uservars[key].value;
|
||||
} else if (this.config.vars[key].type.substr(0, 5) == 'calc:') {
|
||||
const requiresArray = this.config.vars[key].detail.multiple ? true : false;
|
||||
const isArray = Array.isArray(vars[key]);
|
||||
|
||||
if (requiresArray && !isArray) {
|
||||
vars[key] = [ vars[key] ];
|
||||
} else if (!requiresArray && isArray) {
|
||||
const isSelect = this.config.vars[key].detail.type == 'select';
|
||||
|
||||
let val = undefined;
|
||||
|
||||
if (this.config.uservars)
|
||||
val = this.config.uservars[key]?.value;
|
||||
|
||||
if (val && !vars[key].some(v1 => v1 == val)) {
|
||||
val = undefined;
|
||||
delete this.config.uservars[key].value;
|
||||
}
|
||||
if (isSelect && val && !this.config.vars[key].detail.options.some(option => option.value == val)) {
|
||||
val = undefined;
|
||||
delete this.config.uservars[key].value;
|
||||
}
|
||||
|
||||
if (!val)
|
||||
val = vars[key].find(Boolean); // get first element
|
||||
|
||||
let detail = { ...this.config.vars[key].detail, type: 'select' };
|
||||
|
||||
if (this.config.vars[key].detail.type == 'select') {
|
||||
detail.options = this.config.vars[key].detail.options
|
||||
.filter(option => vars[key].some(v => v == option.value));
|
||||
} else {
|
||||
detail.options = vars[key].map(value => ({ label: value, value }));
|
||||
}
|
||||
|
||||
const sharedData = this.sharedData || {};
|
||||
sharedData[key] = detail;
|
||||
this.$emit('update:sharedData', sharedData);
|
||||
this.$emit('setConfig', Vue.markRaw(ConfigGraph));
|
||||
|
||||
vars[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.calledVars = vars;
|
||||
|
||||
try {
|
||||
const result = await this.$api.call(
|
||||
ApiReport.get(this.config.statistik_kurzbz, vars),
|
||||
{ errorHandling: false }
|
||||
);
|
||||
this.data = result.data;
|
||||
} catch(error) {
|
||||
const hasEmptyUserValues = Object.entries(this.config.vars)
|
||||
.some(([key, v]) => {
|
||||
if (v.type != 'user')
|
||||
return false;
|
||||
if (!this.config.uservars)
|
||||
return true;
|
||||
if (!this.config.uservars[key])
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
if (hasEmptyUserValues)
|
||||
return this.hasErrors = true;
|
||||
|
||||
if (error.response.data.errors)
|
||||
return this.hasErrors = error.response.data.errors;
|
||||
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getValueForVar } = useCalculatedVars();
|
||||
return {
|
||||
getValueForVar
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!this.adminMode) {
|
||||
if (Object.values(this.config.vars).some(v => v.type == 'user'))
|
||||
this.$emit('setConfig', Vue.markRaw(ConfigGraph));
|
||||
else if (this.config.charts.length > 1)
|
||||
this.$emit('setConfig', Vue.markRaw(ConfigGraph));
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-graph w-100 h-100 d-flex flex-column justify-content-center align-items-center">
|
||||
<report-error v-if="hasErrors" :errors="hasErrors" />
|
||||
<template v-else-if="chartSetup !== false">
|
||||
<fhc-chart ref="chart" :chart-options="chartSetup" />
|
||||
</template>
|
||||
<i v-else class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
import AbstractWidget from '../Abstract.js';
|
||||
import ReportError from './Error/Error.js';
|
||||
import ConfigKpi from './Config/Kpi.js';
|
||||
|
||||
import { useCalculatedVars } from '../../../composables/DashboardWidget/Report/CalculatedVars.js';
|
||||
|
||||
import ApiReport from '../../../api/factory/report.js';
|
||||
|
||||
export default {
|
||||
name: "WidgetsReportKpi",
|
||||
components: {
|
||||
ReportError,
|
||||
ConfigKpi,
|
||||
},
|
||||
mixins: [ AbstractWidget ],
|
||||
inject: {
|
||||
adminMode: {
|
||||
from: 'adminMode',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
calledVars: {},
|
||||
data: undefined,
|
||||
hasErrors: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
activeLabel() {
|
||||
if (!this.activeAggregator.label)
|
||||
return '';
|
||||
|
||||
return this.activeAggregator.label.replace(
|
||||
/\{\s*([^\}]+)\s*\}/g,
|
||||
(match, key) => {
|
||||
if (this.calledVars[key] === undefined)
|
||||
return match;
|
||||
if (this.config.vars[key].detail?.options) {
|
||||
const options = this.config.vars[key].detail.options;
|
||||
const res = options.find(opt => opt.value == this.calledVars[key]);
|
||||
if (res?.label)
|
||||
return res.label;
|
||||
}
|
||||
return this.calledVars[key];
|
||||
}
|
||||
);
|
||||
},
|
||||
activeAggregator() {
|
||||
if (this.config.aggregators.length == 1)
|
||||
return this.config.aggregators[0];
|
||||
|
||||
if (this.config.aggregator !== undefined)
|
||||
return this.config.aggregators[this.config.aggregator];
|
||||
return this.config.aggregators.find(agg => agg.default);
|
||||
},
|
||||
filteredData() {
|
||||
if (!this.data)
|
||||
return null;
|
||||
if (!this.activeAggregator)
|
||||
return null;
|
||||
if (this.activeAggregator.type == 'count')
|
||||
return this.data.length;
|
||||
if (this.activeAggregator.type == 'sum') {
|
||||
const fields = this.activeAggregator.fields.split(',');
|
||||
return this.data.reduce(
|
||||
(res, curr) => res + fields.reduce(
|
||||
(sum, key) => sum + (curr[key] || 0),
|
||||
0
|
||||
),
|
||||
0
|
||||
);
|
||||
}
|
||||
},
|
||||
kpi() {
|
||||
if (this.adminMode)
|
||||
return '?';
|
||||
if (this.filteredData !== null)
|
||||
return this.filteredData;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config: {
|
||||
handler() { this.fetchData(); },
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
if (this.configMode)
|
||||
return;
|
||||
|
||||
this.hasErrors = false;
|
||||
|
||||
let vars = {};
|
||||
for (var key in this.config.vars) {
|
||||
vars[key] = await this.getValueForVar(this.config.vars[key]);
|
||||
|
||||
if (this.config.vars[key].type == 'user') {
|
||||
if (!this.config.uservars || !this.config.uservars[key])
|
||||
delete vars[key];
|
||||
else
|
||||
vars[key] = this.config.uservars[key].value;
|
||||
} else if (this.config.vars[key].type.substr(0, 5) == 'calc:') {
|
||||
const requiresArray = this.config.vars[key].detail.multiple ? true : false;
|
||||
const isArray = Array.isArray(vars[key]);
|
||||
|
||||
if (requiresArray && !isArray) {
|
||||
vars[key] = [ vars[key] ];
|
||||
} else if (!requiresArray && isArray) {
|
||||
const isSelect = this.config.vars[key].detail.type == 'select';
|
||||
|
||||
let val = undefined;
|
||||
|
||||
if (this.config.uservars)
|
||||
val = this.config.uservars[key]?.value;
|
||||
|
||||
if (val && !vars[key].some(v1 => v1 == val)) {
|
||||
val = undefined;
|
||||
delete this.config.uservars[key].value;
|
||||
}
|
||||
if (isSelect && val && !this.config.vars[key].detail.options.some(option => option.value == val)) {
|
||||
val = undefined;
|
||||
delete this.config.uservars[key].value;
|
||||
}
|
||||
|
||||
if (!val)
|
||||
val = vars[key].find(Boolean); // get first element
|
||||
|
||||
let detail = { ...this.config.vars[key].detail, type: 'select' };
|
||||
|
||||
if (this.config.vars[key].detail.type == 'select') {
|
||||
detail.options = this.config.vars[key].detail.options
|
||||
.filter(option => vars[key].some(v => v == option.value));
|
||||
} else {
|
||||
detail.options = vars[key].map(value => ({ label: value, value }));
|
||||
}
|
||||
|
||||
const sharedData = this.sharedData || {};
|
||||
sharedData[key] = detail;
|
||||
this.$emit('update:sharedData', sharedData);
|
||||
this.$emit('setConfig', Vue.markRaw(ConfigKpi));
|
||||
|
||||
vars[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.calledVars = vars;
|
||||
|
||||
try {
|
||||
const result = await this.$api.call(
|
||||
ApiReport.get(this.config.statistik_kurzbz, vars),
|
||||
{ errorHandling: false }
|
||||
);
|
||||
this.data = result.data;
|
||||
} catch(error) {
|
||||
if (this.activeAggregator === undefined)
|
||||
return this.hasErrors = true;
|
||||
|
||||
const hasEmptyUserValues = Object.entries(this.config.vars)
|
||||
.some(([key, v]) => {
|
||||
if (v.type != 'user')
|
||||
return false;
|
||||
if (!this.config.uservars)
|
||||
return true;
|
||||
if (!this.config.uservars[key])
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
if (hasEmptyUserValues)
|
||||
return this.hasErrors = true;
|
||||
|
||||
if (error.response.data.errors)
|
||||
return this.hasErrors = error.response.data.errors;
|
||||
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getValueForVar } = useCalculatedVars();
|
||||
return {
|
||||
getValueForVar
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!this.adminMode) {
|
||||
if (Object.values(this.config.vars).some(v => v.type == 'user'))
|
||||
this.$emit('setConfig', Vue.markRaw(ConfigKpi));
|
||||
else if (this.config.aggregators.length > 1)
|
||||
this.$emit('setConfig', Vue.markRaw(ConfigKpi));
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-report-kpi w-100 h-100 d-flex flex-column justify-content-center align-items-center">
|
||||
<report-error v-if="hasErrors" :errors="hasErrors" />
|
||||
<template v-else-if="kpi !== false">
|
||||
<div class="h1 text-center">{{ kpi }}</div>
|
||||
<small>{{ activeLabel }}</small>
|
||||
</template>
|
||||
<i v-else class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -27,6 +27,7 @@ export default {
|
||||
name: String,
|
||||
containerClass: [String, Array, Object],
|
||||
label: String,
|
||||
labelClass: [String, Array, Object],
|
||||
// NOTE(chris): remove these from $attrs array to prevent doubled event listeners
|
||||
onInput: [Array, Function],
|
||||
'onUpdate:modelValue': [Array, Function],
|
||||
@@ -63,7 +64,7 @@ export default {
|
||||
if (this.noAutoClass)
|
||||
return this.acc;
|
||||
|
||||
const acc = {...this.acc};
|
||||
const acc = { ...this.acc };
|
||||
|
||||
if (this.inputGroup)
|
||||
acc['input-group-item'] = true;
|
||||
@@ -78,6 +79,31 @@ export default {
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
alc() {
|
||||
if (!this.labelClass || !this.label)
|
||||
return {};
|
||||
if (typeof this.labelClass === 'string' || this.labelClass instanceof String)
|
||||
return this.labelClass.split(' ').reduce((a,c) => {a[c] = true; return a}, {});
|
||||
if (Array.isArray(this.labelClass))
|
||||
return this.labelClass.reduce((a,c) => {a[c] = true; return a}, {});
|
||||
return this.labelClass;
|
||||
},
|
||||
autoLabelClass() {
|
||||
if (!this.label)
|
||||
return {};
|
||||
|
||||
if (this.noAutoClass)
|
||||
return this.alc;
|
||||
|
||||
const alc = { ...this.alc };
|
||||
|
||||
if (this.lcType == 'radio' || this.lcType == 'checkbox')
|
||||
alc['form-check-label'] = true;
|
||||
else
|
||||
alc['form-label'] = true;
|
||||
|
||||
return alc;
|
||||
},
|
||||
lcType() {
|
||||
if (!this.type)
|
||||
return 'text';
|
||||
@@ -243,7 +269,7 @@ 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>
|
||||
<label v-if="label && lcType != 'radio' && lcType != 'checkbox'" :class="autoLabelClass" :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)">
|
||||
@@ -323,7 +349,7 @@ export default {
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
<label v-if="label && (lcType == 'radio' || lcType == 'checkbox')" :for="idCmp" :class="!noAutoClass && 'form-check-label'" v-html="label"></label>
|
||||
<label v-if="label && (lcType == 'radio' || lcType == 'checkbox')" :for="idCmp" :class="autoLabelClass" v-html="label"></label>
|
||||
<div v-if="valid !== undefined && feedback.length && !noFeedback" :class="feedbackClass">
|
||||
<template v-for="(msg, i) in feedback" :key="i">
|
||||
<hr v-if="i" class="m-0">
|
||||
|
||||
@@ -61,7 +61,6 @@ export default {
|
||||
selected: [],
|
||||
studiengang_kz: null,
|
||||
selectedStudiensemester: this.defaultSemester,
|
||||
preselected: null,
|
||||
dropdowns: {
|
||||
studiensemester_array: [],
|
||||
sprachen_array: [],
|
||||
@@ -74,11 +73,7 @@ export default {
|
||||
calcheightonly: true,
|
||||
types: [
|
||||
"mitarbeiter",
|
||||
"mitarbeiter_ohne_zuordnung",
|
||||
/*
|
||||
"lehreinheit",
|
||||
"lehrveranstaltung",
|
||||
*/
|
||||
"mitarbeiter_ohne_zuordnung"
|
||||
],
|
||||
actions: {
|
||||
employee: {
|
||||
@@ -91,26 +86,6 @@ export default {
|
||||
childactions: [
|
||||
]
|
||||
},
|
||||
lehreinheit: {
|
||||
defaultaction: {
|
||||
type: "function",
|
||||
action: (data) => {
|
||||
this.onSelectLehreinheit(data);
|
||||
}
|
||||
},
|
||||
childactions: [
|
||||
]
|
||||
},
|
||||
lehrveranstaltung: {
|
||||
defaultaction: {
|
||||
type: "function",
|
||||
action: (data) => {
|
||||
this.onSelectLehrveranstaltung(data);
|
||||
}
|
||||
},
|
||||
childactions: [
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -119,8 +94,6 @@ export default {
|
||||
filter() {
|
||||
let filter = {
|
||||
emp: this.$route.params.emp,
|
||||
le: this.$route.params.le,
|
||||
lv: this.$route.params.lv,
|
||||
studiensemester_kurzbz: this.selectedStudiensemester
|
||||
};
|
||||
let index;
|
||||
@@ -132,7 +105,7 @@ export default {
|
||||
if (index > -1)
|
||||
filter.semester = this.$route.params.treemenu[index+1];
|
||||
}
|
||||
filter.activeFilter = filter.emp ? 'employee' : filter.stg ? 'verband' : filter.le ? 'lehreinheit' : filter.lv ? 'lehrveranstaltung' : null;
|
||||
filter.activeFilter = filter.emp ? 'employee' : filter.stg ? 'verband' : null;
|
||||
return filter;
|
||||
},
|
||||
emp() {
|
||||
@@ -230,50 +203,20 @@ export default {
|
||||
},
|
||||
onSelectEmployee(emp)
|
||||
{
|
||||
let params = { ...this.$route.params }
|
||||
delete params.le;
|
||||
delete params.lv;
|
||||
|
||||
this.$router.push({
|
||||
name: 'emp',
|
||||
params: {
|
||||
...params,
|
||||
...this.$route.params,
|
||||
emp
|
||||
}
|
||||
});
|
||||
},
|
||||
onSelectLehreinheit(lehreinheit)
|
||||
{
|
||||
this.$router.push({
|
||||
name: 'le',
|
||||
params: {
|
||||
le: lehreinheit.id,
|
||||
stdsem: lehreinheit.studiensemester_kurzbz.toLowerCase()
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onSelectLehrveranstaltung(lehrveranstaltung)
|
||||
{
|
||||
this.$router.push({
|
||||
name: 'lv',
|
||||
params: {
|
||||
lv: lehrveranstaltung.id,
|
||||
stdsem: lehrveranstaltung?.studiensemester_kurzbz?.toLowerCase() ?? this.selectedStudiensemester.toLowerCase()
|
||||
}
|
||||
});
|
||||
},
|
||||
onSelectVerband({ path: link, stg_kz })
|
||||
{
|
||||
|
||||
let params = { ...this.$route.params }
|
||||
delete params.le;
|
||||
delete params.lv;
|
||||
|
||||
this.$router.push({
|
||||
name: this.$route.name == 'emp' ? 'emp' : 'treemenu',
|
||||
params: {
|
||||
...params,
|
||||
...this.$route.params,
|
||||
treemenu: link.split('/')
|
||||
}
|
||||
});
|
||||
@@ -352,7 +295,7 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$route.name !== 'le' && this.$route.name !== 'lv' && !this.$route.params.stdsem) {
|
||||
if (!this.$route.params.stdsem) {
|
||||
this.$router.replace({
|
||||
name: 'stdsem',
|
||||
params: {
|
||||
@@ -534,8 +477,7 @@ export default {
|
||||
<template #top>
|
||||
<lv-table ref="lvTable"
|
||||
v-model:selected="selected"
|
||||
@row-clicked="handleRowClicked"
|
||||
@select-LV="onSelectLehrveranstaltung"
|
||||
@row-clicked="handleRowClicked"
|
||||
:filter="filter"
|
||||
>
|
||||
<template #filterzuruecksetzen v-if="filter.activeFilter === 'employee'">
|
||||
|
||||
@@ -57,8 +57,7 @@ export default {
|
||||
},
|
||||
emits: [
|
||||
'update:selected',
|
||||
'row-clicked',
|
||||
'selectLV',
|
||||
'row-clicked'
|
||||
],
|
||||
watch: {
|
||||
filter: {
|
||||
@@ -107,24 +106,8 @@ export default {
|
||||
{
|
||||
event: 'dataProcessed',
|
||||
handler: (data) => {
|
||||
if (this.$route.params.le)
|
||||
{
|
||||
this.$refs.table.tabulator.selectRow(this.$refs.table.tabulator.getRows());
|
||||
}
|
||||
else if (this.$route.params.lv)
|
||||
{
|
||||
this.$refs.table.tabulator.selectRow(this.$refs.table.tabulator.getRows());
|
||||
|
||||
this.allRows = this.getAllRows(this.$refs.table.tabulator.getRows());
|
||||
this.allRows.forEach(row => {
|
||||
row.treeExpand()
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
this.reexpandRows()
|
||||
this.$emit('update:selected', {})
|
||||
}
|
||||
this.reexpandRows()
|
||||
this.$emit('update:selected', {})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -238,35 +221,35 @@ export default {
|
||||
selectableRows: true,
|
||||
rowContextMenu: (component, e) => {
|
||||
|
||||
if (e.getData()?.lehreinheit_id === undefined || !e.getData()?.lehreinheit_id)
|
||||
return [];
|
||||
if (e.getData()?.lehreinheit_id === undefined)
|
||||
return;
|
||||
return [
|
||||
{
|
||||
label: this.$p.t('lehre', 'lvteilkopieren'),
|
||||
label: "LV-Teil kopieren",
|
||||
menu: [
|
||||
{
|
||||
label: this.$p.t('lehre', 'alles'),
|
||||
label: "Alles",
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "alle");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$p.t('lehre', 'nurlvteil'),
|
||||
label: "Nur LV-Teil",
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "lvteil");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$p.t('lehre', 'nurmitgruppen'),
|
||||
label: "Nur mit Gruppen",
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "gruppen");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$p.t('lehre', 'nurmitlehrende'),
|
||||
label: "Nur mit Lehrenden",
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "lektoren");
|
||||
@@ -275,21 +258,7 @@ export default {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: this.$p.t('lehre', 'zurlvspringen'),
|
||||
action: (e, row) => {
|
||||
this.openLV(row)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$p.t('lehre', 'lvteillinkkopieren'),
|
||||
action: (e, row) => {
|
||||
let rowData = row.getData();
|
||||
let link = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/LVVerwaltung/stdsem/' + rowData.studiensemester_kurzbz.toLowerCase() + '/le/' + rowData.lehreinheit_id;
|
||||
navigator.clipboard.writeText(link);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$p.t('ui', 'entfernen'),
|
||||
label: "Entfernen",
|
||||
action: (e, row) => {
|
||||
this.deleteLehreinheit(row)
|
||||
},
|
||||
@@ -386,7 +355,7 @@ export default {
|
||||
},
|
||||
buildApiUrl()
|
||||
{
|
||||
if (!['emp', 'treemenu', 'le', 'lv'].includes(this.$route.name))
|
||||
if (!['emp', 'treemenu'].includes(this.$route.name))
|
||||
return null;
|
||||
|
||||
let url = 'stdsem/' + this.$route.params.stdsem;
|
||||
@@ -395,14 +364,6 @@ export default {
|
||||
url += '/emp/' + this.$route.params.emp;
|
||||
}
|
||||
|
||||
if (this.$route.params.le) {
|
||||
url += '/le/' + this.$route.params.le;
|
||||
}
|
||||
|
||||
if (this.$route.params.lv) {
|
||||
url += '/lv/' + this.$route.params.lv;
|
||||
}
|
||||
|
||||
if (this.$route.params.treemenu) {
|
||||
url += '/' + this.$route.params.treemenu.join('/');
|
||||
}
|
||||
@@ -541,17 +502,6 @@ export default {
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
openLV(row)
|
||||
{
|
||||
let rowData = row.getData();
|
||||
|
||||
|
||||
let data = {
|
||||
id: rowData.lehrveranstaltung_id,
|
||||
studiensemester_kurzbz: rowData.studiensemester_kurzbz
|
||||
}
|
||||
this.$emit('selectLV', data);
|
||||
},
|
||||
getAllRows(rows)
|
||||
{
|
||||
let result = [];
|
||||
|
||||
@@ -263,7 +263,7 @@ export default {
|
||||
this.$refs[modalRef].hide();
|
||||
},
|
||||
reload() {
|
||||
this.$refs.table?.reloadTable();
|
||||
this.$refs.table.reloadTable();
|
||||
},
|
||||
searchFirma(event) {
|
||||
if (this.abortController.firmen) {
|
||||
|
||||
+2
-15
@@ -30,9 +30,6 @@ export default {
|
||||
computed: {
|
||||
betreuerFormOpened() {
|
||||
return this.newMode || this.editMode;
|
||||
},
|
||||
isMitarbeiter(){
|
||||
return this.autocompleteSelectedBetreuer?.name?.includes('Mitarbeiter') ?? false;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@@ -433,11 +430,6 @@ export default {
|
||||
},
|
||||
actionKontaktdatenBearbeiten() {
|
||||
if (!this.autocompleteSelectedBetreuer) return;
|
||||
if (this.isMitarbeiter)
|
||||
{
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'editingMitarbeiterPVonly'));
|
||||
return;
|
||||
}
|
||||
this.$refs.kontaktdatenModal.show();
|
||||
},
|
||||
// stuff to do after new person has been saved
|
||||
@@ -502,12 +494,7 @@ export default {
|
||||
<button class="btn btn-outline-secondary" @click="actionNewPerson">{{ $p.t('projektarbeit', 'neuePersonAnlegen') }}</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button
|
||||
class="btn btn-outline-secondary float-end"
|
||||
@click="actionKontaktdatenBearbeiten"
|
||||
>
|
||||
{{ $p.t('projektarbeit', 'kontaktdatenBearbeiten') }}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary float-end" @click="actionKontaktdatenBearbeiten">{{ $p.t('projektarbeit', 'kontaktdatenBearbeiten') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -618,7 +605,7 @@ export default {
|
||||
</contact>
|
||||
</div>
|
||||
</div>
|
||||
</bs-modal>
|
||||
|
||||
</bs-modal>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import TemplateFrame from "./template/frame.js";
|
||||
|
||||
export default {
|
||||
name: 'SearchbarResultTeachingUnit',
|
||||
components: {
|
||||
TemplateFrame
|
||||
},
|
||||
emits: [ 'actionexecuted' ],
|
||||
props: {
|
||||
res: Object,
|
||||
actions: Object
|
||||
},
|
||||
template: `
|
||||
<template-frame
|
||||
class="searchbar-result-teachingunit"
|
||||
:res="res"
|
||||
:actions="actions"
|
||||
:title="res.bezeichnung"
|
||||
:image-fallback="res.foto"
|
||||
@actionexecuted="$emit('actionexecuted')"
|
||||
>
|
||||
<div class="searchbar_table">
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label"> {{ res.type === 'lehreinheit' ? $p.t('lehre/lehreinheit') : $p.t('lehre/lehrveranstaltung') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/studiengang') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.studiengang }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow" v-if="res.oe_bezeichnung">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/organisationseinheit') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.oe_bezeichnung }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow" v-if="res.studiensemester_kurzbz">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/studiensemester') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.studiensemester_kurzbz }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/ausbildungssemester') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.semester }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbar_tablerow">
|
||||
<div class="searchbar_tablecell searchbar_label">{{ $p.t('lehre/lehrform') }}</div>
|
||||
<div class="searchbar_tablecell searchbar_value">
|
||||
{{ res.lehrform_kurzbz }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template-frame>`
|
||||
};
|
||||
@@ -8,7 +8,6 @@ import dms from "./result/dms.js";
|
||||
import cms from "./result/cms.js";
|
||||
import mergedStudent from "./result/mergedstudent.js";
|
||||
import mergedPerson from "./result/mergedperson.js";
|
||||
import teachingunit from "./result/teachingunit.js";
|
||||
import { debounce } from "../../helpers/DebounceHelper.js";
|
||||
|
||||
export default {
|
||||
@@ -24,7 +23,6 @@ export default {
|
||||
cms,
|
||||
mergedStudent,
|
||||
mergedPerson,
|
||||
teachingunit,
|
||||
},
|
||||
props: {
|
||||
searchoptions: {
|
||||
|
||||
@@ -4,7 +4,11 @@ export default {
|
||||
defaultRatio: {
|
||||
type: Array,
|
||||
default: () => [50, 50]
|
||||
}
|
||||
},
|
||||
useDivHeight: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
@@ -61,9 +65,16 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
calcHeights: function() {
|
||||
var windowheight = window.innerHeight;
|
||||
var windowheight = 0;
|
||||
if (this.useDivHeight) {
|
||||
windowheight = this.$refs.verticalsplit.offsetHeight - this.$refs.vsplitter.offsetHeight;
|
||||
this.selfOffsetTop = 0;
|
||||
} else {
|
||||
windowheight = window.innerHeight;
|
||||
this.selfOffsetTop = this.$refs.verticalsplit.offsetTop;
|
||||
}
|
||||
|
||||
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 ) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (C) 2026 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import CurrentStudienjahr from './CalculatedVars/CurrentStudienjahr.js';
|
||||
import CalcDate from './CalculatedVars/CalcDate.js';
|
||||
import UserStgs from './CalculatedVars/UserStgs.js';
|
||||
|
||||
export function useCalculatedVars() {
|
||||
let calculations = {
|
||||
CurrentStudienjahr,
|
||||
CalcDate,
|
||||
UserStgs,
|
||||
};
|
||||
|
||||
const components = Vue.ref({});
|
||||
const options = Vue.ref([]);
|
||||
|
||||
const $p = Vue.inject('$p');
|
||||
const $api = Vue.inject('$api');
|
||||
const $fhcAlert = Vue.inject('$fhcAlert');
|
||||
|
||||
for (var key in calculations) {
|
||||
let label = calculations[key].label;
|
||||
if (Array.isArray(label)) {
|
||||
label = $p.t(label);
|
||||
}
|
||||
options.value.push({
|
||||
value: key,
|
||||
label,
|
||||
});
|
||||
if (calculations[key].component) {
|
||||
components.value[key] = Vue.markRaw(calculations[key].component);
|
||||
}
|
||||
}
|
||||
|
||||
async function getValueForVar(variable) {
|
||||
if (variable.type.match(/^calc:/)) {
|
||||
const instructions = variable.type.split(':');
|
||||
|
||||
if (calculations[instructions[1]]?.calculate)
|
||||
return calculations[instructions[1]].calculate.bind({
|
||||
$p,
|
||||
$api,
|
||||
$fhcAlert,
|
||||
})(instructions);
|
||||
}
|
||||
return variable?.value;
|
||||
}
|
||||
|
||||
return {
|
||||
components,
|
||||
options,
|
||||
getValueForVar,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (C) 2026 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import CalcDate from '../../../../components/Dashboard/WidgetAdmin/Edit/Report/Vars/Calc/Date.js';
|
||||
|
||||
export default {
|
||||
label: [ 'dashboard', 'widget_report_vars_calc_date' ],
|
||||
component: CalcDate,
|
||||
async calculate(instructions) {
|
||||
let duration = instructions[2] || 'P';
|
||||
if (duration[0] == '-')
|
||||
duration = luxon.Duration.fromISO(duration.substr(1)).negate();
|
||||
else
|
||||
duration = luxon.Duration.fromISO(duration);
|
||||
|
||||
return luxon.DateTime.now().plus(duration);
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (C) 2026 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ApiStudienjahr from '../../../../api/factory/studienjahr.js';
|
||||
|
||||
export default {
|
||||
label: [ 'dashboard', 'widget_report_vars_calc_currentstudienjahr' ],
|
||||
async calculate(instructions) {
|
||||
try {
|
||||
const result = await this.$api.call(ApiStudienjahr.getAktNext());
|
||||
return result.data.studienjahr_kurzbz;
|
||||
} catch(error) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (C) 2026 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import CalcRight from '../../../../components/Dashboard/WidgetAdmin/Edit/Report/Vars/Calc/Right.js';
|
||||
|
||||
import ApiStudiengang from '../../../../api/factory/studiengang.js';
|
||||
|
||||
export default {
|
||||
label: [ 'dashboard', 'widget_report_vars_calc_stgs' ],
|
||||
component: CalcRight,
|
||||
async calculate(instructions) {
|
||||
try {
|
||||
const result = await this.$api.call(ApiStudiengang.getAllowed(instructions[2]));
|
||||
return result.data;
|
||||
} catch(error) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
@@ -28,6 +28,7 @@ const copyObject = function (obj, copyArray) {
|
||||
const highchartsPlugin = {
|
||||
|
||||
install(app, options) {
|
||||
options = options || {};
|
||||
|
||||
function destroyChart() {
|
||||
if (this.chart) {
|
||||
|
||||
@@ -408,7 +408,7 @@ GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE dashboard.tbl_dashboard_widget TO vil
|
||||
--
|
||||
|
||||
GRANT SELECT ON TABLE dashboard.tbl_widget TO web;
|
||||
GRANT SELECT,INSERT,UPDATE ON TABLE dashboard.tbl_widget TO vilesci;
|
||||
GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE dashboard.tbl_widget TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
@@ -466,3 +466,22 @@ if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berecht
|
||||
}
|
||||
}
|
||||
|
||||
// Add delete privilege for vilesci on tbl_widget
|
||||
if ($result = @$db->db_query("
|
||||
SELECT 1
|
||||
FROM information_schema.role_table_grants
|
||||
WHERE grantee = 'vilesci'
|
||||
AND table_name = 'tbl_widget'
|
||||
AND table_schema = 'dashboard'
|
||||
AND privilege_type = 'DELETE'
|
||||
LIMIT 1")) {
|
||||
if ($db->db_num_rows($result) == 0) {
|
||||
$qry = "GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE dashboard.tbl_widget TO vilesci;";
|
||||
|
||||
if(!$db->db_query($qry)) {
|
||||
echo '<strong>dashboard.tbl_widget '.$db->db_last_error().'</strong><br>';
|
||||
} else {
|
||||
echo 'dashboard.tbl_widget: Added delete privilege for vilesci<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+922
-162
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
INSERT INTO lehre.tbl_projektarbeit
|
||||
(projektarbeit_id, projekttyp_kurzbz, titel, lehreinheit_id, student_uid, firma_id, note, punkte, beginn, ende, faktor, freigegeben, gesperrtbis, stundensatz, themenbereich, anmerkung, updateamum, updatevon, insertamum, insertvon, ext_id, gesamtstunden, titel_english, sprache, seitenanzahl, abgabedatum, kontrollschlagwoerter, schlagwoerter, schlagwoerter_en, abstract, abstract_en, "final", lehrveranstaltung_id, studiensemester_kurzbz) VALUES
|
||||
(1, 'Bachelor', 'Auswirkung der Sonneneinstrahlung auf die Gesundheit', 46111, 's46b413', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1)),
|
||||
(2, 'Bachelor', 'Neue Unterrichtsmethoden mit KI', 46111, 's46b414', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1)),
|
||||
(3, 'Bachelor', 'Betrugsbekämpfung bei Anwesenheitskontrolle', 46111, 's46b415', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1)),
|
||||
(4, 'Bachelor', 'Neuronale Netze', 46111, 's46b416', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true, 4611, NearestWintersemester(1))
|
||||
(projektarbeit_id, projekttyp_kurzbz, titel, lehreinheit_id, student_uid, firma_id, note, punkte, beginn, ende, faktor, freigegeben, gesperrtbis, stundensatz, themenbereich, anmerkung, updateamum, updatevon, insertamum, insertvon, ext_id, gesamtstunden, titel_english, sprache, seitenanzahl, abgabedatum, kontrollschlagwoerter, schlagwoerter, schlagwoerter_en, abstract, abstract_en, "final") VALUES
|
||||
(1, 'Bachelor', 'Auswirkung der Sonneneinstrahlung auf die Gesundheit', 46111, 's46b413', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true),
|
||||
(2, 'Bachelor', 'Neue Unterrichtsmethoden mit KI', 46111, 's46b414', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true),
|
||||
(3, 'Bachelor', 'Betrugsbekämpfung bei Anwesenheitskontrolle', 46111, 's46b415', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true),
|
||||
(4, 'Bachelor', 'Neuronale Netze', 46111, 's46b416', NULL, NULL, NULL, NULL, NULL, NULL, true, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-12 14:06:10.000', 'anondata', NULL, 3.0000, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, true)
|
||||
;
|
||||
|
||||
INSERT INTO lehre.tbl_projektbetreuer
|
||||
|
||||
Reference in New Issue
Block a user