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
+ )
+);
+?>
+
+
+
+
+
+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...
+
+
widget[4] = v" :widget="widget[5]" :style="{'--core-dashboard-order-sm':widget[1],'--core-dashboard-order-md':widget[2],'--core-dashboard-order-lg':widget[3]}" @remove="removeWidget(widget[0])">
+
+
+
+
+
+
+
+
+
+
+
+

+
+
{{widget.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
+
+
+
+
+
+ {{val}}
+
+
`
+}
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: ``
+}
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: ``
+}