mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-12 15:04:04 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa026515a5 | ||
|
|
3d8c4fa4d0 | ||
|
|
272014bd78 | ||
|
|
76a8e00edf | ||
|
|
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 | ||
|
|
d40040efd8 |
@@ -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'
|
||||
]
|
||||
];
|
||||
|
||||
@@ -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
|
||||
@@ -101,6 +109,41 @@ class Widget extends FHCAPI_Controller
|
||||
$tmpsetup = json_decode($widget->setup);
|
||||
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
|
||||
$widget->setup = $tmpsetup;
|
||||
$widget->permitted = empty($widget->berechtigung_kurzbz)
|
||||
|| $this->permissionlib->isBerechtigt($widget->berechtigung_kurzbz);
|
||||
|
||||
return $widget;
|
||||
}, $widgets);
|
||||
|
||||
$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);
|
||||
|
||||
@@ -134,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'); ?>
|
||||
+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',
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -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');
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import BsModal from "../Bootstrap/Modal.js";
|
||||
import HeightTransition from "../Tranistion/HeightTransition.js";
|
||||
import MissingPermissionWidget from "../DashboardWidget/MissingPermission.js";
|
||||
|
||||
import { enableDragDropTouch } from "../../../../vendor/drag-drop-touch-js/dragdroptouch/dist/drag-drop-touch.esm.min.js";
|
||||
|
||||
@@ -112,6 +113,12 @@ export default {
|
||||
isPinned() {
|
||||
return this.place?.pinned ? true : false;
|
||||
},
|
||||
permitted() {
|
||||
// widgets without a linked permission (or before the template loaded)
|
||||
// are considered permitted; the backend sets permitted === false only
|
||||
// when the user is missing the widget's required permission
|
||||
return this.widgetTemplate?.permitted !== false;
|
||||
},
|
||||
ready() {
|
||||
return this.component && this.arguments !== null;
|
||||
}
|
||||
@@ -138,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) {
|
||||
@@ -156,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];
|
||||
}
|
||||
}
|
||||
@@ -169,18 +179,22 @@ export default {
|
||||
&& this.widgetTemplate.widget_id
|
||||
&& this.widgetTemplate.arguments
|
||||
) {
|
||||
let component = (await import(this.widgetTemplate.setup.file)).default;
|
||||
// render the "missing permission" screen instead of the actual
|
||||
// widget when the user does not hold the widget's linked permission
|
||||
let component = this.permitted
|
||||
? (await import(this.widgetTemplate.setup.file)).default
|
||||
: MissingPermissionWidget;
|
||||
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;
|
||||
},
|
||||
@@ -269,7 +283,7 @@ export default {
|
||||
</template>
|
||||
<!-- widget link -->
|
||||
<a
|
||||
v-if="widgetTemplate.setup.cis4link"
|
||||
v-if="widgetTemplate.setup.cis4link && permitted"
|
||||
:href="getWidgetC4Link(widgetTemplate)"
|
||||
class="col-auto ms-auto"
|
||||
:aria-label="$p.t('dashboard/widget_link')"
|
||||
@@ -331,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"
|
||||
|
||||
@@ -14,10 +14,14 @@ export default {
|
||||
callbacks: {}
|
||||
}),
|
||||
computed: {
|
||||
// filter widgets away the user has no permissions for
|
||||
availableWidgets() {
|
||||
return (this.widgets || []).filter(widget => widget.permitted !== false);
|
||||
},
|
||||
hasAnyWidgets() {
|
||||
if (!this.widgets)
|
||||
return false;
|
||||
if (!this.widgets.length && !this.hiddenWidgets?.length)
|
||||
if (!this.availableWidgets.length && !this.hiddenWidgets?.length)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -54,7 +58,7 @@ export default {
|
||||
<template v-slot:default>
|
||||
<template v-if="widgets && hiddenWidgets">
|
||||
<div
|
||||
v-if="!widgets.length && !hiddenWidgets.length"
|
||||
v-if="!availableWidgets.length && !hiddenWidgets.length"
|
||||
class="row g-2"
|
||||
>
|
||||
<div>{{ $p.t('dashboard/noWidgetsAvailable') }}</div>
|
||||
@@ -74,16 +78,17 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="widgets.length"
|
||||
v-if="availableWidgets.length"
|
||||
class="row g-2"
|
||||
>
|
||||
<div
|
||||
v-for="widget in widgets"
|
||||
<template
|
||||
v-for="widget in availableWidgets"
|
||||
:key="widget.widget_id"
|
||||
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
|
||||
>
|
||||
<widget-icon @select="pick" :widget="widget"></widget-icon>
|
||||
</div>
|
||||
<div v-if="widget.permitted" class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2">
|
||||
<widget-icon @select="pick" :widget="widget"></widget-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -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,21 @@
|
||||
import AbstractWidget from './Abstract.js';
|
||||
|
||||
/**
|
||||
* Fallback screen rendered in place of a widget when the current user is missing
|
||||
* the permission (berechtigung_kurzbz) linked to that widget. Used for widgets
|
||||
* that are already on a user's dashboard but whose required permission the user
|
||||
* no longer holds.
|
||||
*/
|
||||
export default {
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
created() {
|
||||
this.$emit('setConfig', false);
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="dashboard-widget-missing-permission d-flex flex-column justify-content-center align-items-center text-center h-100 p-3 text-body-secondary">
|
||||
<i class="fa-solid fa-lock fa-2x mb-2" aria-hidden="true"></i>
|
||||
<p class="mb-0">{{ $p.t('dashboard/widget_missing_permission') }}</p>
|
||||
</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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -103,7 +103,7 @@ require_once('dbupdate_3.4/76160_lvv_favorites.php');
|
||||
require_once('dbupdate_3.4/77080_tabulator_presets_table.php');
|
||||
require_once('dbupdate_3.4/78292_gehaltsanpassungtyp.php');
|
||||
require_once('dbupdate_3.4/54225_reihungstest_ablauf_fuer_quereinsteiger.php');
|
||||
|
||||
require_once('dbupdate_3.4/77757_widgets_berechtigungen.php');
|
||||
|
||||
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
|
||||
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Beschreibung:
|
||||
* Dashboard Widgets: Verknüpfung einer (einzelnen) Berechtigung pro Widget.
|
||||
* Neue Spalte dashboard.tbl_widget.berechtigung_kurzbz mit FK auf
|
||||
* system.tbl_berechtigung. Ist die Spalte NULL, erfordert das Widget keine
|
||||
* gesonderte Berechtigung. Benutzer denen die verknüpfte Berechtigung fehlt,
|
||||
* bekommen das Widget nicht mehr zur Auswahl angezeigt bzw. sehen (falls bereits
|
||||
* am Dashboard) einen "Fehlende Berechtigung"-Screen.
|
||||
*/
|
||||
if (! defined('DB_NAME')) exit('No direct script access allowed');
|
||||
|
||||
// Add column dashboard.tbl_widget.berechtigung_kurzbz (FK => system.tbl_berechtigung)
|
||||
if ($result = @$db->db_query("
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'dashboard'
|
||||
AND table_name = 'tbl_widget'
|
||||
AND column_name = 'berechtigung_kurzbz';"))
|
||||
{
|
||||
if ($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = "
|
||||
ALTER TABLE dashboard.tbl_widget
|
||||
ADD COLUMN berechtigung_kurzbz VARCHAR(32) NULL;
|
||||
|
||||
ALTER TABLE dashboard.tbl_widget
|
||||
ADD CONSTRAINT tbl_widget_berechtigung_fk
|
||||
FOREIGN KEY (berechtigung_kurzbz)
|
||||
REFERENCES system.tbl_berechtigung (berechtigung_kurzbz)
|
||||
ON UPDATE CASCADE ON DELETE SET NULL;";
|
||||
|
||||
if (!$db->db_query($qry))
|
||||
{
|
||||
echo '<strong>dashboard.tbl_widget: '.$db->db_last_error().'</strong><br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo 'dashboard.tbl_widget: Spalte berechtigung_kurzbz (FK system.tbl_berechtigung) hinzugefuegt<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,6 +748,26 @@ $phrases = array(
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'global',
|
||||
'phrase' => 'label',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Bezeichnung',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Label',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'global',
|
||||
@@ -1353,6 +1373,46 @@ $phrases = array(
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'global',
|
||||
'phrase' => 'permission',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Berechtigung',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Permission',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'global',
|
||||
'phrase' => 'permissions',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Berechtigungen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Permissions',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'global',
|
||||
@@ -1994,6 +2054,26 @@ $phrases = array(
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
'phrase' => 'default',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Standard',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Default',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
@@ -49620,6 +49700,26 @@ array(
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_missing_permission',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Ihnen fehlt die erforderliche Berechtigung, um dieses Widget anzuzeigen.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'You do not have the permission required to display this widget.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
// CIS4 phrases from legacy code end
|
||||
// FHC4 Phrases Abschlusspruefung
|
||||
array(
|
||||
@@ -60850,6 +60950,846 @@ I have been informed that I am under no obligation to consent to the transmissio
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widgets',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Widgets',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Widgets',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Widget',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Widget',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'no_widget_selected',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Bitte ein Widget markieren!',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Please select a widget!',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'confirm_unsaved_progress',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Das Widget wurde geändert! Änderungen verwerfen?',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'The widget has been modified! Discard changes?',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_error_delete_native',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Native Widgets können nicht gelöscht werden!',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Native widgets can not be deleted!',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_id',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Widget ID',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Widget ID',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_kurzbz',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Widget Kurzbz',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Widget short',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_icon',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Icon',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Icon',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'generator',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Generator',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Generator',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_setup_size',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Größe',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Size',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_setup_width',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Breite',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Width',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_setup_height',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Höhe',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Height',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_setup_hidefooter',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Footer verstecken',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Hide footer',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_statistik',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Statistik',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Statistic',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_statistik_change',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Die Statistik wurde geändert! Einstellungen für vorherige Statistik verwerfen?',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'The Statistic has been changed! Discard settings for the previous Statistic?',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Variablen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Variables',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_var_type_fix',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Fester Wert',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Fixed',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_var_type_calc',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Berechnet',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Calculated',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_var_type_user',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Benutzerdefiniert',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'User defined',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_calc_date',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Berechnetes Datum',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Calculated date',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_calc_currentstudienjahr',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Aktuelles Studienjahr',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Current academic year',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_calc_stgs',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Studiengang des Users',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Users degree program',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_calc_date',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Berechnetes Datum',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Calculated date',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_calc_date_placeholder',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Dauer (fügen Sie am Anfang ein \'-\' hinzu, um es abzuziehen) ab dem aktuellen Datum',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Duration (add \'-\' at the beginning to substract) from current date',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_vars_calc_right_placeholder',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Berechtigungen (Komma getrennt)',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Access Rights (comma separated)',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_kpi_aggregators',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Aggregatoren',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Aggregators',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_kpi_aggregator',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Aggregator',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Aggregator',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_kpi_aggregator_type_sum',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Summe',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Sum',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_kpi_aggregator_type_count',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Zeilenanzahl',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Row count',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_kpi_aggregator_fields',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Felder',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Fields',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_kpi_aggregator_fields_placeholder',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Felder (Komma getrennt)',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Fields (comma separated)',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_field',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'DB Feld',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'DB Field',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_axis',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'X-Achse',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'X-Axis',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Chart',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Chart',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_charts',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Charts',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Charts',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart_layout',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Layout',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Layout',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart_type_bar',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Balken',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Bar',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart_type_line',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Linie',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Line',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart_type_dashed',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Strichlierte Linie',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Dashed line',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart_type_dotted',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Gepunktete Linie',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Dotted line',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart_bg_color',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Farbe',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Color',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'dashboard',
|
||||
'phrase' => 'widget_report_graph_chart_text_color',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Textfarbe',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Text color',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'ui',
|
||||
|
||||
Reference in New Issue
Block a user