diff --git a/application/config/navigation.php b/application/config/navigation.php index cd71b02ca..0a1c55bf5 100644 --- a/application/config/navigation.php +++ b/application/config/navigation.php @@ -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' + ] +]; diff --git a/application/controllers/api/frontend/v1/dashboard/Widget.php b/application/controllers/api/frontend/v1/dashboard/Widget.php index ac8c682e8..3c14f985c 100644 --- a/application/controllers/api/frontend/v1/dashboard/Widget.php +++ b/application/controllers/api/frontend/v1/dashboard/Widget.php @@ -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,12 @@ 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', + 'generators' => 'dashboard/admin:rw', ]); // Libraries @@ -107,6 +114,38 @@ class Widget extends FHCAPI_Controller $this->terminateWithSuccess($widgets); } + public function listAll() + { + $result = $this->WidgetModel->load(); + + $widgets = $this->getDataOrTerminateWithError($result); + + $widgets = array_map(function ($widget) { + $widget->arguments = json_decode($widget->arguments); + $tmpsetup = json_decode($widget->setup); + $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); + $widget->setup = $tmpsetup; + return $widget; + }, $widgets); + + $this->terminateWithSuccess($widgets); + } + + public function listAllOriginal() + { + $result = $this->WidgetModel->load(); + + $widgets = $this->getDataOrTerminateWithError($result); + + $widgets = array_map(function ($widget) { + $widget->arguments = json_decode($widget->arguments); + $widget->setup = json_decode($widget->setup); + return $widget; + }, $widgets); + + $this->terminateWithSuccess($widgets); + } + public function setAllowed() { $this->load->library('form_validation'); @@ -134,4 +173,149 @@ class Widget extends FHCAPI_Controller $this->terminateWithSuccess($data); } + + public function create() + { + $this->loadPhrases(['mobility', 'dashboard']); + $this->load->library('form_validation'); + + $this->form_validation->set_rules( + 'widget_kurzbz', + $this->p->t('mobility', 'kurzbz'), + 'required|is_not_in_db[dashboard/Widget_model:widget_kurzbz]', + [ + 'is_not_in_db' => 'TODO(chris): error phrase' + ] + ); + $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 = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function update() + { + $this->loadPhrases(['zeitaufzeichnung']); + $this->load->library('form_validation'); + + $this->form_validation->set_rules( + 'widget_id', + $this->p->t('zeitaufzeichnung', '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 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; + } } diff --git a/application/controllers/dashboard/Admin.php b/application/controllers/dashboard/Admin.php index 702c04bab..77aa5657b 100644 --- a/application/controllers/dashboard/Admin.php +++ b/application/controllers/dashboard/Admin.php @@ -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', [9]); + } + public function preview($dashboard_kurzbz = 'CIS') { $this->load->view('dashboard/preview.php', [ diff --git a/application/helpers/hlp_common_helper.php b/application/helpers/hlp_common_helper.php index 06cfa1cfd..5059ba142 100644 --- a/application/helpers/hlp_common_helper.php +++ b/application/helpers/hlp_common_helper.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 diff --git a/application/views/dashboard/widgets.php b/application/views/dashboard/widgets.php new file mode 100644 index 000000000..f35fa8f44 --- /dev/null +++ b/application/views/dashboard/widgets.php @@ -0,0 +1,32 @@ +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/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 + ) +); +?> + +
+ +load->view('templates/FHC-Footer'); ?> diff --git a/public/css/Fhc.css b/public/css/Fhc.css index 2c8966366..cc4b95627 100644 --- a/public/css/Fhc.css +++ b/public/css/Fhc.css @@ -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 { diff --git a/public/js/api/factory/dashboard/widget.js b/public/js/api/factory/dashboard/widget.js index d7d1bbc86..a8d2d2010 100644 --- a/public/js/api/factory/dashboard/widget.js +++ b/public/js/api/factory/dashboard/widget.js @@ -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,25 @@ 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 + }; + }, + generators() { + return { + method: 'get', + url: '/api/frontend/v1/dashboard/widget/generators' + }; + }, }; \ No newline at end of file diff --git a/public/js/apps/Dashboard/WidgetAdmin.js b/public/js/apps/Dashboard/WidgetAdmin.js new file mode 100644 index 000000000..c81b5f563 --- /dev/null +++ b/public/js/apps/Dashboard/WidgetAdmin.js @@ -0,0 +1,41 @@ +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 */` + + ` +}); +app.use(primevue.config.default, { + zIndex: { + overlay: 9000, + tooltip: 8000 + } +}) +app.use(PluginsPhrasen); +app.directive('tooltip', primevue.tooltip); +app.mount('#main'); \ No newline at end of file diff --git a/public/js/components/Dashboard/WidgetAdmin.js b/public/js/components/Dashboard/WidgetAdmin.js new file mode 100644 index 000000000..33ad326c9 --- /dev/null +++ b/public/js/components/Dashboard/WidgetAdmin.js @@ -0,0 +1,77 @@ +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, + }, + 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) { + // TODO(chris): modify data with base info + this.currentData = JSON.parse(JSON.stringify(data)); + this.save(); + }, + save() { + this.select(this.currentData); + this.$nextTick(() => { + this.$refs.list.$refs.table.tabulator + .updateOrAddData([this.originalData]) + .then(res => { + res[0].select(); + }) + .catch(this.$fhcAlert.handleSystemError); + }); + }, + }, + template: /* html */` + ` +} diff --git a/public/js/components/Dashboard/WidgetAdmin/Edit.js b/public/js/components/Dashboard/WidgetAdmin/Edit.js new file mode 100644 index 000000000..376a65def --- /dev/null +++ b/public/js/components/Dashboard/WidgetAdmin/Edit.js @@ -0,0 +1,76 @@ +import CoreForm from "../../Form/Form.js"; +import EditBasics from "./Edit/Basics.js"; + +import ApiWidget from "../../../api/factory/dashboard/widget.js"; + +export default { + name: 'WidgetsAdminEdit', + components: { + CoreForm, + EditBasics, + }, + props: { + originalData: Object, + modelValue: Object, + unsavedProgress: Boolean, + }, + emits: [ + "saved", + "update:modelValue", + ], + data() { + return { + generator: null, + saving: false, + }; + }, + watch: { + 'originalData.setup.generator': { + async handler(gen) { + if (!gen) + return this.generator = null; + + // TODO(chris): extensions + let file = '../../DashboardWidget/Generators/' + gen + '.js'; + + 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); + }, + }, + template: /* html */` +