From 522e341d3dcb32d7916178a22f598c950f5d039d Mon Sep 17 00:00:00 2001 From: Cris Date: Tue, 6 Sep 2022 15:29:42 +0200 Subject: [PATCH] Dashboard Test V1 --- application/controllers/Test.php | 51 +++++ .../api/v1/dashboard/Dashboard.php | 54 +++++ .../controllers/api/v1/dashboard/User.php | 89 ++++++++ .../models/dashboard/Dashboard_model.php | 18 ++ .../dashboard/Dashboardpreset_model.php | 28 +++ .../dashboard/Dashboardwidget_model.php | 29 +++ application/models/dashboard/Widget_model.php | 18 ++ application/views/test/Test.php | 63 ++++++ public/js/apps/Test.js | 15 ++ public/js/components/CoreDashboard.js | 206 ++++++++++++++++++ public/js/components/DBW/KPI.js | 26 +++ public/js/components/Dashboard/DragAndDrop.js | 139 ++++++++++++ public/js/components/Dashboard/Item.js | 52 +++++ 13 files changed, 788 insertions(+) create mode 100644 application/controllers/Test.php create mode 100644 application/controllers/api/v1/dashboard/Dashboard.php create mode 100644 application/controllers/api/v1/dashboard/User.php create mode 100644 application/models/dashboard/Dashboard_model.php create mode 100644 application/models/dashboard/Dashboardpreset_model.php create mode 100644 application/models/dashboard/Dashboardwidget_model.php create mode 100644 application/models/dashboard/Widget_model.php create mode 100644 application/views/test/Test.php create mode 100644 public/js/apps/Test.js create mode 100644 public/js/components/CoreDashboard.js create mode 100644 public/js/components/DBW/KPI.js create mode 100644 public/js/components/Dashboard/DragAndDrop.js create mode 100644 public/js/components/Dashboard/Item.js diff --git a/application/controllers/Test.php b/application/controllers/Test.php new file mode 100644 index 000000000..aa21f6296 --- /dev/null +++ b/application/controllers/Test.php @@ -0,0 +1,51 @@ + 'user:r', + ) + ); + + $this->load->library('AuthLib'); + $this->load->library('WidgetLib'); + + $this->_setAuthUID(); // sets property uid + + $this->setControllerId(); // sets the controller id + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + public function index() + { + $this->load->view('test/Test.php', []); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Private methods + + /** + * Retrieve the UID of the logged user and checks if it is valid + */ + private function _setAuthUID() + { + $this->_uid = getAuthUID(); + + if (!$this->_uid) show_error('User authentification failed'); + } + +} diff --git a/application/controllers/api/v1/dashboard/Dashboard.php b/application/controllers/api/v1/dashboard/Dashboard.php new file mode 100644 index 000000000..28365525b --- /dev/null +++ b/application/controllers/api/v1/dashboard/Dashboard.php @@ -0,0 +1,54 @@ + 'user:r' + ]); + } + + /** + * @return void + */ + public function getWidgets() + { + $this->load->model('dashboard/Dashboard_model', 'DashboardModel'); + $result = $this->DashboardModel->loadWhere(['name'=>$this->get('dashboard')]); + if (isError($result)) + return $this->response($result, REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + if (!hasData($result)) + return $this->response('No Dashboard with the name "' . $this->get('dashboard') . '" found.', REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + $dashboard_id = current(getData($result))->dashboard_id; + + $this->load->model('dashboard/Dashboardwidget_model', 'DashboardwidgetModel'); + $result = $this->DashboardwidgetModel->loadWidgets($dashboard_id); + if (isError($result)) + return $this->response($result, REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + if (!hasData($result)) + return $this->response('No Widgets for Dashboard "' . $this->get('dashboard') . '" found.', REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + + $this->response(getData($result), REST_Controller::HTTP_OK); + } + + protected function _check_whitelist_auth() {} + +} diff --git a/application/controllers/api/v1/dashboard/User.php b/application/controllers/api/v1/dashboard/User.php new file mode 100644 index 000000000..fdc5d7a1e --- /dev/null +++ b/application/controllers/api/v1/dashboard/User.php @@ -0,0 +1,89 @@ + 'user:r', + 'Widget' => 'user:r', + 'Widgets' => 'user:r' + ]); + } + + /** + * @return void + */ + public function getAuthObj() + { + $result = $this->authlib->getAuthObj(); + + $this->response($result, REST_Controller::HTTP_OK); + } + + /** + * @return void + */ + public function getWidget() + { + $this->load->model('dashboard/Widget_model', 'WidgetModel'); + $result = $this->WidgetModel->load($this->get('widget_id')); + if (isError($result)) + return $this->response($result, REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + if (!hasData($result)) + return $this->response('No Widget with the id "' . $this->get('widget_id') . '" found.', REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + $result = current(getData($result)); + + $this->response($result, REST_Controller::HTTP_OK); + } + + /** + * @return void + */ + public function getWidgets() + { + $this->load->model('dashboard/Dashboard_model', 'DashboardModel'); + $result = $this->DashboardModel->loadWhere(['name'=>$this->get('dashboard')]); + if (isError($result)) + return $this->response($result, REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + if (!hasData($result)) + return $this->response('No Dashboard with the name "' . $this->get('dashboard') . '" found.', REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + $dashboard_id = current(getData($result))->dashboard_id; + + $authObj = $this->authlib->getAuthObj(); + + $this->load->model('dashboard/Dashboardpreset_model', 'DashboardpresetModel'); + $result = $this->DashboardpresetModel->loadForUser($dashboard_id, $authObj->username); + if (isError($result)) + return $this->response($result, REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + if (!hasData($result)) + return $this->response('No Dashboard for user "' . $authObj->username . '" found.', REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + + $result = (current(getData($result))->config); + $result = json_decode($result); + if ($result === null) + return $this->response(json_last_error_msg(), REST_Controller::HTTP_INTERNAL_SERVER_ERROR); + + $this->response($result, REST_Controller::HTTP_OK); + } + + protected function _check_whitelist_auth() {} + +} diff --git a/application/models/dashboard/Dashboard_model.php b/application/models/dashboard/Dashboard_model.php new file mode 100644 index 000000000..2aa567282 --- /dev/null +++ b/application/models/dashboard/Dashboard_model.php @@ -0,0 +1,18 @@ +dbTable = "( WITH vals (dashboard_id, name) AS (VALUES + (0,'CIS'), + (1,'PV21') + ) SELECT * FROM vals ) AS tbl_dashboard"; + $this->pk = 'dashboard_id'; + } + +} diff --git a/application/models/dashboard/Dashboardpreset_model.php b/application/models/dashboard/Dashboardpreset_model.php new file mode 100644 index 000000000..070fb77be --- /dev/null +++ b/application/models/dashboard/Dashboardpreset_model.php @@ -0,0 +1,28 @@ +dbTable = "( WITH vals (dashboard_preset_id, config) AS (VALUES + (0,CONCAT('[[0',',','0',',','0',',','0',',','[]]',',','[1',',','2',',','2',',','2',',','{\"display\":2}]]')), + (1,CONCAT('[[0',',','1',',','0',',','1',',','{}]',',','[1',',','0',',','1',',','0',',','{\"display\":1}]]')) + ) SELECT * FROM vals ) AS tbl_dashboard_preset"; + $this->pk = 'dashboard_preset_id'; + } + + public function loadForUser($dashboard_id, $uid) + { + $this->addJoin("( WITH vals (dashboard_preset_user_id, uid, dashboard_id, dashboard_preset_id) AS (VALUES + (0,'ma0168', 0, 0) + ) SELECT * FROM vals ) AS tbl_dashboard_preset_user", 'dashboard_preset_id'); + return $this->loadWhere(['uid' => $uid, 'dashboard_id' => $dashboard_id]); + + return success(); + } + +} diff --git a/application/models/dashboard/Dashboardwidget_model.php b/application/models/dashboard/Dashboardwidget_model.php new file mode 100644 index 000000000..e9349aae4 --- /dev/null +++ b/application/models/dashboard/Dashboardwidget_model.php @@ -0,0 +1,29 @@ +dbTable = "( WITH vals (dashboard_widget_id, dashboard_id, widget_id) AS (VALUES + (0,0,0), + (1,0,1), + (2,1,0) + ) SELECT * FROM vals ) AS tbl_dashboard_widget"; + $this->pk = 'dashboard_widget_id'; + } + + public function loadWidgets($dashboard_id) + { + $this->addJoin("( WITH vals (widget_id, name, component_name, component_path, arguments) AS (VALUES + (0,'KPI Single','DbwKpi','./DBW/KPI.js',CONCAT('{\"data\":[1]',',','\"display\":0}')), + (1,'KPI Multi','DbwKpi','./DBW/KPI.js',CONCAT('{\"data\":[1,2,3]}')) + ) SELECT * FROM vals ) AS tbl_widget", 'widget_id'); + + return $this->loadWhere(['dashboard_id' => $dashboard_id]); + } + +} diff --git a/application/models/dashboard/Widget_model.php b/application/models/dashboard/Widget_model.php new file mode 100644 index 000000000..c5d39e99d --- /dev/null +++ b/application/models/dashboard/Widget_model.php @@ -0,0 +1,18 @@ +dbTable = "( WITH vals (widget_id, name, component_name, component_path, arguments) AS (VALUES + (0,'KPI Single','DbwKpi','./DBW/KPI.js',CONCAT('{\"data\":[1]',',','\"display\":0}')), + (1,'KPI Multi','DbwKpi','./DBW/KPI.js',CONCAT('{\"data\":[1,2,3]}')) + ) SELECT * FROM vals ) AS tbl_widget"; + $this->pk = 'widget_id'; + } + +} diff --git a/application/views/test/Test.php b/application/views/test/Test.php new file mode 100644 index 000000000..84a2a5ece --- /dev/null +++ b/application/views/test/Test.php @@ -0,0 +1,63 @@ +load->view('templates/FHC-Header', + array( + 'title' => 'FH-Complete', + 'bootstrap5' => true, + 'fontawesome6' => true, + 'axios027' => true, + 'restclient' => true, + 'vue3' => true, + 'customJSModules' => ['public/js/apps/Test.js'], + 'navigationcomponent' => true + ) +); +?> + + + +
+ + + +
+
+

Dashboard

+
+ + + +
+
+ +load->view('templates/FHC-Footer'); ?> diff --git a/public/js/apps/Test.js b/public/js/apps/Test.js new file mode 100644 index 000000000..ec6e74a9b --- /dev/null +++ b/public/js/apps/Test.js @@ -0,0 +1,15 @@ +import {CoreNavigationCmpt} from '../components/navigation/Navigation.js'; +import CoreDashboard from '../components/CoreDashboard.js'; + +Vue.createApp({ + data: () => ({ + appSideMenuEntries: {} + }), + components: { + CoreNavigationCmpt, + CoreDashboard/*, + "CoreFilterCmpt": CoreFilterCmpt, + "verticalsplit": verticalsplit, + "searchbar": searchbar*/ + } +}).mount('#main'); diff --git a/public/js/components/CoreDashboard.js b/public/js/components/CoreDashboard.js new file mode 100644 index 000000000..e6d5c77ac --- /dev/null +++ b/public/js/components/CoreDashboard.js @@ -0,0 +1,206 @@ +import CoreDashboardItem from './Dashboard/Item.js'; +import DragAndDrop from './Dashboard/DragAndDrop.js'; + +export default { + components: { + CoreDashboardItem, + DragAndDrop + }, + data: () => ({ + widgetCache: {}, + componentCache: {}, + widgetWizard: null, + allowedWidgets: [], + widgets: [], + name: '', + newMode: false, + editMode: false + }), + computed: { + loaded: function() { + return this.widgets && this.name; + } + }, + props: [ + "dashboard" + ], + methods: { + saveConfig() { + // TODO(chris): SAVE! + console.log('SAVE', this.widgets); + this.editMode = false; + }, + startWidgetWizard() { + // TODO(chris): load widgets! + let self = this; + axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/Dashboard/Widgets', { + headers: { + 'FHC-API-KEY':'itservice@technikum-wien.at' + }, + params: { + dashboard: this.dashboard + } + }).then(function(result) {console.log(result); + self.allowedWidgets = result.data; + }); + this.widgetWizard.show(); + }, + newWidget(widget) { + let self = this; + let newWidget = [widget.widget_id, this.widgets.length, this.widgets.length, this.widgets.length, []]; + // TODO(chris): loadingscreen? + (new Promise(function (resolve, reject) { + if (self.widgetCache[widget.widget_id]) { + resolve(self.widgetCache[widget.widget_id]); + } else { + this.extendWidgetAndCache(widget).then(widget => { + resolve(widget); + }); + } + })).then(widget => { + newWidget[5] = widget; + self.widgets.push(newWidget); + self.widgetWizard.hide(); + }); + }, + removeWidget(id) { + if (confirm('Are you sure you want to delete this widget?')) + this.widgets = this.widgets.filter(widget => widget[0] != id); + }, + getWidget(widget_id) { + let self = this; + return new Promise(function(resolve, reject) { + if (self.widgetCache[widget_id]) + return resolve(self.widgetCache[widget_id]); + axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/User/Widget', { + headers: { + 'FHC-API-KEY':'itservice@technikum-wien.at' + }, + params: { + widget_id: widget_id + } + }) + .then(result => self.extendWidgetAndCache(result.data)) + .then(widget => resolve(widget)); + }); + }, + extendWidgetAndCache(widget) { + let self = this; + return new Promise(function(resolve, reject) { + let name = widget.component_name; + widget.arguments = JSON.parse(widget.arguments); + widget.component_name = widget.component_name.replace(/[A-Z]/g, (m,o) => (o > 0 ? "-" : "") + m.toLowerCase()); + self.getComponent(name, widget.component_path).then(component => { + widget.component = component; + return widget; + }).then(() => { + self.widgetCache[widget.widget_id] = widget; + resolve(self.widgetCache[widget.widget_id]); + }); + }); + }, + getComponent(name, path) { + let self = this; + return new Promise(async function(resolve, reject) { + if (self.componentCache[name]) + return resolve(self.componentCache[name]); + + + self.componentCache[name] = (await import(path)).default; + resolve(self.componentCache[name]); + }); + } + }, + mounted() { + let self = this; + this.widgetWizard = new bootstrap.Modal(this.$refs.widgetWizard); + axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/User/AuthObj', { + headers: { + 'FHC-API-KEY':'itservice@technikum-wien.at' + } + }).then(function(result) { + self.name = result.data.name; + }); + axios.get('/fhcomplete/index.ci.php/api/v1/dashboard/User/Widgets', { + headers: { + 'FHC-API-KEY':'itservice@technikum-wien.at' + }, + params: { + dashboard: this.dashboard + } + }).then(function(result) { + let promises = []; + result.data.forEach(function(item) { + promises.push(new Promise(function(resolve, reject) { + self.getWidget(item[0]).then(function(widget) { + item[5] = widget; + resolve(); + }); + })); + }); + Promise.all(promises).then(function() { + self.widgets = result.data; + }) + }); + }, + template: `
+
+ +
+

+ Hallo {{name}}! + + + + + +

+
Loading...
+
+ +
+
+
+ +
+
+
+
+ +
` +} diff --git a/public/js/components/DBW/KPI.js b/public/js/components/DBW/KPI.js new file mode 100644 index 000000000..3346685f8 --- /dev/null +++ b/public/js/components/DBW/KPI.js @@ -0,0 +1,26 @@ +export default { + props: [ + "configMode", + "config" + ], + methods: { + changeConfig() { + this.config.display = parseInt(this.$refs.display.value); + this.$emit('config', this.config); + } + }, + template: `
+
KPI Widget
+
+ + +
+ +
` +} diff --git a/public/js/components/Dashboard/DragAndDrop.js b/public/js/components/Dashboard/DragAndDrop.js new file mode 100644 index 000000000..cd24c0ef6 --- /dev/null +++ b/public/js/components/Dashboard/DragAndDrop.js @@ -0,0 +1,139 @@ +export default { + data: () => ({ + isMounted: 0, + movedObjects: [], + tmpStyle: { + display: 'none', + background: 'gray', + 'grid-column-start': 0, + 'grid-column-end': 0, + 'grid-row-start': 0, + 'grid-row-end': 0, + } + }), + props: [ + "width", + "height", + "items" + ], + computed: { + gridWidth() { + if(!this.isMounted) + return 0; + return window.getComputedStyle(this.$refs.container).getPropertyValue('grid-template-columns').split(" ").length; + }, + gridHeight() { + if (!this.gridWidth) + return 0; + let minH = 0; + this.items.forEach(item => { + // TODO(chris): item change is not detected? + minH = Math.max(minH, item.y + item.h - 1); + }); + return Math.max(1, minH); + }, + gridOccupiers() { + let occupiers = []; + let gridWidth = this.gridWidth; + this.items.forEach(item => { + for (var i = 0; i < item.w; i++) + for (var j = 0; j < item.h; j++) + occupiers[(item.y+j) * gridWidth + (item.x+i)] = item.id; + }); + return occupiers; + } + }, + methods: { + startDrag(evt, item) { + evt.dataTransfer.dropEffect = 'move'; + evt.dataTransfer.effectAllowed = 'move'; + evt.dataTransfer.setData('itemId', item.id) + evt.dataTransfer.setData('itemW', item.w) + evt.dataTransfer.setData('itemH', item.h) + console.log(evt.target.style.display); + }, + moveItem(item) { + // TODO(chris): IMPLEMENT + if (!item._x) + item._x = item.x; + item.x++; + }, + moveItemBack(item) { + item.x = item._x; + }, + onDragOver(evt) { + this.tmpStyle.display = 'block'; + + let x = Math.floor(this.gridWidth * (evt.layerX / this.$refs.container.clientWidth)) + 1; + let y = Math.floor(this.gridHeight * (evt.layerY / this.$refs.container.clientHeight)) + 1; + let w = parseInt(evt.dataTransfer.getData('itemW')); + let h = parseInt(evt.dataTransfer.getData('itemH')); + + while (x + w > this.gridWidth + 1) + x--; + + // TODO(chris): start + let id = 0; + while (id = this.movedObjects.pop()) + /*this.items[id].c = this.items[id]._c;*/ + this.moveItemBack(this.items[id]); + for (var i = 0; i < w; i++) { + for (var j = 0; j < h; j++) { + let id = (y+j) * this.gridWidth + (x+i); + if (this.gridOccupiers[id] && this.gridOccupiers[id] != evt.dataTransfer.getData('itemID')) { + + /*if (!this.items[this.gridOccupiers[id]]._c) + this.items[this.gridOccupiers[id]]._c = this.items[this.gridOccupiers[id]].c; + this.items[this.gridOccupiers[id]].c = 'grey';*/ + this.moveItem(this.items[this.gridOccupiers[id]]); + + this.movedObjects.push(this.gridOccupiers[id]); + } + } + } + // TODO(chris): end + + this.tmpStyle['grid-column-start'] = x; + this.tmpStyle['grid-column-end'] = x + w; + this.tmpStyle['grid-row-start'] = y; + this.tmpStyle['grid-row-end'] = y + h; + }, + onDrop(evt, list) { + this.tmpStyle.display = 'none'; + + let id = evt.dataTransfer.getData('itemId'); + let x = Math.floor(this.gridWidth * (evt.layerX / this.$refs.container.clientWidth)) + 1; + let y = Math.floor(this.gridHeight * (evt.layerY / this.$refs.container.clientHeight)) + 1; + let w = parseInt(evt.dataTransfer.getData('itemW')); + + while (x + w > this.gridWidth + 1) + x--; + + this.items.forEach(item => { + if (id == item.id) { + item.x = x; + item.y = y; + console.log(item, id); + } + }); + // TODO(chris): find better way to trigger change for gridHeight + this.isMounted++ + } + }, + watchers: { + items() { + console.log(this.items); + } + }, + mounted() { + this.isMounted = 1; + window.addEventListener('resize', e => { this.isMounted ? this.isMounted++ : 0 }) + }, + template: `
+
+
+
{{gridWidth2}} +
+
+
` +} diff --git a/public/js/components/Dashboard/Item.js b/public/js/components/Dashboard/Item.js new file mode 100644 index 000000000..9a956a3ce --- /dev/null +++ b/public/js/components/Dashboard/Item.js @@ -0,0 +1,52 @@ +export default { + components: {}, + data: () => ({ + configMode: false, + name: '', + component: '', + arguments: null + }), + props: [ + "widget", + "config", + "editMode" + ], + computed: { + ready() { + return this.name && this.component && this.arguments !== null; + } + }, + methods: { + changeConfig(v) { + this.arguments = v; + this.configMode = false; + // TODO(chris): diff arguments widget.arguments + this.$emit('change', v); + } + }, + mounted() { + let self = this; + + if (!this.isPlaceholder) { + this.$options.components[this.widget.component_name] = this.widget.component; + this.name = this.widget.name; + this.component = this.widget.component_name; + this.arguments = {...this.widget.arguments, ...this.config}; + } + }, + template: `
+
+
+
+ + {{name}} + + +
+
+ +
+
+
+
` +}