diff --git a/application/controllers/components/SearchBar.php b/application/controllers/components/SearchBar.php index 41cadbe8e..1212555e4 100644 --- a/application/controllers/components/SearchBar.php +++ b/application/controllers/components/SearchBar.php @@ -34,8 +34,12 @@ class SearchBar extends FHC_Controller */ public function search() { - $searchstr = $this->input->post(self::SEARCHSTR_PARAM); - $types = $this->input->post(self::TYPES_PARAM); + //$searchstr = $this->input->post(self::SEARCHSTR_PARAM); + //$types = $this->input->post(self::TYPES_PARAM); + + $json = json_decode($this->input->raw_input_stream, true); + $searchstr = $json[self::SEARCHSTR_PARAM]; + $types = $json[self::TYPES_PARAM]; $this->outputJson($this->searchbarlib->search($searchstr, $types)); } diff --git a/application/controllers/system/TestSearch.php b/application/controllers/system/TestSearch.php new file mode 100644 index 000000000..1f5c66a1d --- /dev/null +++ b/application/controllers/system/TestSearch.php @@ -0,0 +1,44 @@ + 'system/developer:r' + ) + ); + + // Loads WidgetLib + $this->load->library('WidgetLib'); + + // Loads phrases system + $this->loadPhrases( + array( + 'global', + 'ui', + 'filter' + ) + ); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * Everything has a beginning + */ + public function index() + { + $this->load->view('system/logs/testSearch.php'); + } +} diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index d72aa750f..56f6f6098 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -191,7 +191,7 @@ class SearchBarLib /** * */ - private function raum($searchstr, $type) + private function _raum($searchstr, $type) { return array(); } diff --git a/application/views/system/logs/testSearch.php b/application/views/system/logs/testSearch.php new file mode 100644 index 000000000..882b953f5 --- /dev/null +++ b/application/views/system/logs/testSearch.php @@ -0,0 +1,60 @@ + 'Test Search', + 'jquery3' => true, + 'bootstrap5' => true, + 'fontawesome6' => true, + 'tablesorter2' => true, + 'vue3' => true, + 'ajaxlib' => true, + 'jqueryui1' => true, + 'filtercomponent' => true, + 'navigationcomponent' => true, + 'phrases' => array( + 'global' => array('mailAnXversandt'), + 'ui' => array('bitteEintragWaehlen') + ), + 'customCSSs' => array( + 'public/css/components/verticalsplit.css', + 'public/css/components/searchbar.css', + ), + 'customJSs' => array('vendor/axios/axios/axios.min.js'), + 'customJSModules' => array('public/js/apps/TestSearch.js') + ); + + $this->load->view('templates/FHC-Header', $includesArray); +?> + +
+ + + + +
+
+
+ +
+
+
+ + + + + + + + +
+
+
+ +load->view('templates/FHC-Footer', $includesArray); ?> + diff --git a/composer.json b/composer.json index 9513a85b4..670ec8b02 100644 --- a/composer.json +++ b/composer.json @@ -325,6 +325,17 @@ "type": "file" } } + }, + { + "type": "package", + "package": { + "name": "axios/axios", + "version": "0.27.2", + "dist": { + "url": "https://unpkg.com/axios@0.27.2/dist/axios.min.js", + "type": "file" + } + } } ], "require": { @@ -367,7 +378,7 @@ "twbs/bootstrap5": "5.1.*", "vuejs/vuejs3": "3.2.33", - + "axios/axios": "0.27.2", "alvaro-prieto/colresizable": "1.6", diff --git a/public/css/components/searchbar.css b/public/css/components/searchbar.css new file mode 100644 index 000000000..3b5cb788c --- /dev/null +++ b/public/css/components/searchbar.css @@ -0,0 +1,81 @@ +/* + Created on : Jun 15, 2022, 3:21:25 PM + Author : bambi +*/ + +.searchbar_settings { + position: absolute; + z-index: 9999; + background-color: #fff; + border: 2px solid #666; + padding: 1rem; +} + +.searchbar_results { + position: absolute; + z-index: 9998; + background-color: #fff; + border: 2px solid #666; + padding: 1rem; + overflow-y: auto; +} + +.searchbar_result { + border-bottom: 1px solid #666; + margin-bottom: 1rem; +} + +.searchbar_grid { + display: grid; + grid-template-columns: [icon] 100px [data] auto; +} + +.searchbar_icon { + grid-column-start: icon; + grid-column-end: span 1; +} + +.searchbar_data { + grid-column-start: data; + grid-column-end: span 1; +} + +.searchbar_actions { + display: flex; + padding: 0; + margin-top: 1rem; +} + +.searchbar_actions li { + list-style: none; + text-align: center; + padding: 3px; +} + +.searchbar_table { + display: table; +} + +.searchbar_tablerow { + display: table-row; +} + +.searchbar_tablecell { + display: table-cell; + min-width: 175px; + padding: 0 6px; +} + +.searchbar_settings_types { + display: flex; + padding: 0; +} + +.searchbar_settings_types li { + list-style: none; + padding: 5px; +} + +.searchbar_settings_types li label { + padding: 3px; +} \ No newline at end of file diff --git a/public/css/components/verticalsplit.css b/public/css/components/verticalsplit.css new file mode 100644 index 000000000..b4185c5a3 --- /dev/null +++ b/public/css/components/verticalsplit.css @@ -0,0 +1,59 @@ +/* + Created on : May 19, 2022, 10:16:21 AM + Author : bambi +*/ +:root { + --fhc-verticalsplit-vsplitter-bg-color: #eee; + --fhc-verticalsplit-vsplitter-border-color: #eee; + --fhc-verticalsplit-vsplitter-splitactions-color: #000; +} + +.verticalsplitted { + overflow-y: auto; +} + +.verticalsplitter { + text-align: center; +} + +.verticalsplitter.top { + border-top: solid 3px var(--fhc-verticalsplit-vsplitter-border-color); +} + +.verticalsplitter.bottom { + border-bottom: solid 3px var(--fhc-verticalsplit-vsplitter-border-color); +} + +.splitactions { + background-color: var(--fhc-verticalsplit-vsplitter-bg-color); + color: var(--fhc-verticalsplit-vsplitter-splitactions-color); + display: inline-block; + padding: 0 5px 0 5px; +} + +.splitactions.top { + border-radius: 0 0 40% 40%; +} + +.splitactions.bottom { + border-radius: 40% 40% 0 0; +} + +.splitaction { + display: inline-block; + width: 25px; + cursor: pointer; +} + +.splitaction.resize { + cursor: row-resize; +} + +#content { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +#content > div:first-child { + margin-top: 30px; +} \ No newline at end of file diff --git a/public/js/apps/TestSearch.js b/public/js/apps/TestSearch.js new file mode 100644 index 000000000..d4aa35312 --- /dev/null +++ b/public/js/apps/TestSearch.js @@ -0,0 +1,195 @@ +import {CoreFilterCmpt} from '../components/Filter.js'; +import {CoreNavigationCmpt} from '../components/Navigation.js'; +import verticalsplit from "../components/verticalsplit/verticalsplit.js"; +import searchbar from "../components/searchbar/searchbar.js"; +import fhcapifactory from "./api/fhcapifactory.js"; + +Vue.$fhcapi = fhcapifactory; + +Vue.createApp({ + "data": function() { + return { + "title": "Test Search", + "appSideMenuEntries": {}, + "searchbaroptions": { + "types": [ + "person", + "raum", + "mitarbeiter", + "student", + "prestudent", + "document", + "cms", + "organisationunit" + ], + "actions": { + "person": { + "defaultaction": { + "type": "link", + "action": function(data) { + //alert('person defaultaction ' + JSON.stringify(data)); + //window.location.href = data.profil; + return data.profil; + } + }, + "childactions": [ + { + "label": "testchildaction1", + "icon": "fas fa-check-circle", + "type": "function", + "action": function(data) { + alert('person testchildaction 01 ' + JSON.stringify(data)); + } + }, + { + "label": "testchildaction2", + "icon": "fas fa-file-csv", + "type": "function", + "action": function(data) { + alert('person testchildaction 02 ' + JSON.stringify(data)); + } + } + ] + }, + "raum": { + "defaultaction": { + "type": "function", + "action": function(data) { + alert('raum defaultaction ' + JSON.stringify(data)); + } + }, + "childactions": [ + { + "label": "Rauminformation", + "icon": "fas fa-info-circle", + "type": "link", + "action": function(data) { + return data.infolink; + } + }, + { + "label": "Raumreservierung", + "icon": "fas fa-bookmark", + "type": "link", + "action": function(data) { + return data.booklink; + } + } + ] + }, + "employee": { + "defaultaction": { + "type": "function", + "action": function(data) { + alert('employee defaultaction ' + JSON.stringify(data)); + } + }, + "childactions": [ + { + "label": "testchildaction1", + "icon": "fas fa-address-book", + "type": "function", + "action": function(data) { + alert('employee testchildaction 01 ' + JSON.stringify(data)); + } + }, + { + "label": "testchildaction2", + "icon": "fas fa-user-slash", + "type": "function", + "action": function(data) { + alert('employee testchildaction 02 ' + JSON.stringify(data)); + } + }, + { + "label": "testchildaction3", + "icon": "fas fa-bell", + "type": "function", + "action": function(data) { + alert('employee testchildaction 03 ' + JSON.stringify(data)); + } + }, + { + "label": "testchildaction4", + "icon": "fas fa-calculator", + "type": "function", + "action": function(data) { + alert('employee testchildaction 04 ' + JSON.stringify(data)); + } + } + ] + }, + "organisationunit": { + "defaultaction": { + "type": "function", + "action": function(data) { + alert('organisationunit defaultaction ' + JSON.stringify(data)); + } + }, + "childactions": [] + } + } + }, + "searchbaroptions2": { + "types": [ + "raum", + "organisationunit" + ], + "actions": { + "raum": { + "defaultaction": { + "type": "function", + "action": function(data) { + alert('raum defaultaction ' + JSON.stringify(data)); + } + }, + "childactions": [ + { + "label": "Rauminformation", + "icon": "fas fa-info-circle", + "type": "link", + "action": function(data) { + return data.infolink; + } + }, + { + "label": "Raumreservierung", + "icon": "fas fa-bookmark", + "type": "link", + "action": function(data) { + return data.booklink; + } + } + ] + }, + "organisationunit": { + "defaultaction": { + "type": "function", + "action": function(data) { + alert('organisationunit defaultaction ' + JSON.stringify(data)); + } + }, + "childactions": [] + } + } + } + }; + }, + "components": { + "CoreNavigationCmpt": CoreNavigationCmpt, + "CoreFilterCmpt": CoreFilterCmpt, + "verticalsplit": verticalsplit, + "searchbar": searchbar + }, + "methods": { + "newSideMenuEntryHandler": function(payload) { + this.appSideMenuEntries = payload; + }, + "searchfunction": function(searchsettings) { + return Vue.$fhcapi.Search.search(searchsettings); + }, + "searchfunctiondummy": function(searchsettings) { + return Vue.$fhcapi.Search.searchdummy(searchsettings); + } + } +}).mount('#main'); diff --git a/public/js/apps/api/fhcapifactory.js b/public/js/apps/api/fhcapifactory.js new file mode 100644 index 000000000..c1f1e0d5c --- /dev/null +++ b/public/js/apps/api/fhcapifactory.js @@ -0,0 +1,5 @@ +import Search from "./search.js"; + +export default { + "Search": Search +}; diff --git a/public/js/apps/api/search.js b/public/js/apps/api/search.js new file mode 100644 index 000000000..8544d9fe3 --- /dev/null +++ b/public/js/apps/api/search.js @@ -0,0 +1,12 @@ +export default { + search: function(searchsettings) { + const url = FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'index.ci.php/components/SearchBar/search'; + return axios.post(url, searchsettings); + }, + searchdummy: function(searchsettings) { + const url = FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'public/js/apps/api/dummyapi.php/Search'; + return axios.post(url, searchsettings); + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/action.js b/public/js/components/searchbar/action.js new file mode 100644 index 000000000..699d5f8c7 --- /dev/null +++ b/public/js/components/searchbar/action.js @@ -0,0 +1,31 @@ +export default { + props: { + res: { + type: Object + }, + action: { + type: Object + }, + cssclass: { + type: String, + default: '' + } + }, + emits: [ 'actionexecuted' ], + template: ` + + Action + + `, + methods: { + getactionhref: function() { + return (this.action.type === 'link') ? this.action.action(this.res) + : 'javascript:void(0);'; + }, + execaction: function() { + this.action.action(this.res); + this.$emit('actionexecuted'); + } + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/actions.js b/public/js/components/searchbar/actions.js new file mode 100644 index 000000000..ff9e25e12 --- /dev/null +++ b/public/js/components/searchbar/actions.js @@ -0,0 +1,28 @@ +import action from "./action.js"; + +export default { + props: [ "res", "actions" ], + components: { + action: action + }, + emits: [ 'actionexecuted' ], + template: ` + +
+ `, + methods: { + hasicon: function(index) { + return (typeof this.actions[index].icon !== "undefined"); + }, + geticonclass: function(index) { + return this.actions[index].icon; + } + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/employee.js b/public/js/components/searchbar/employee.js new file mode 100644 index 000000000..32f2e5a69 --- /dev/null +++ b/public/js/components/searchbar/employee.js @@ -0,0 +1,76 @@ +import action from "./action.js"; +import actions from "./actions.js"; + +export default { + props: [ "res", "actions" ], + components: { + action: action, + actions: actions + }, + emits: [ 'actionexecuted' ], + template: ` +
+ +
+
+ + + + +
+ +
+ + {{ res.name }} + + +
+ +
+ +
+
OrgEinheit
+
+ {{ res.organisationunit_name }} +
+
+ +
+
EMail
+ +
+ +
+
Phone
+ +
+ +
+ + + +
+
+ +
+ `, + methods: { + }, + computed: { + mailtourl: function() { + return 'mailto:' + this.res.email; + }, + telurl: function() { + return 'tel:' + this.res.phone; + } + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/organisationunit.js b/public/js/components/searchbar/organisationunit.js new file mode 100644 index 000000000..3af3cc937 --- /dev/null +++ b/public/js/components/searchbar/organisationunit.js @@ -0,0 +1,79 @@ +import action from "./action.js"; +import actions from "./actions.js"; + +export default { + props: [ "res", "actions" ], + components: { + action: action, + actions: actions + }, + emits: [ 'actionexecuted' ], + template: ` +
+ +
+
+ + + +
+ +
+ + {{ res.name }} + + +
+ +
+ +
+
übergeordnete OrgEinheit
+
+ {{ res.parentoe_name }} +
+
+ +
+
Gruppen-EMail
+ +
+ +
+
Leiter
+
+ {{ res.leader_name }} +
+
+ +
+
Mitarbeiter-Anzahl
+
+ {{ res.number_of_people }} +
+
+ +
+ + + +
+
+ +
+ `, + methods: { + }, + computed: { + mailtourl: function() { + return 'mailto:' + this.res.mailgroup; + }, + telurl: function() { + return 'tel:' + this.res.phone; + } + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/person.js b/public/js/components/searchbar/person.js new file mode 100644 index 000000000..ba5b4b7f0 --- /dev/null +++ b/public/js/components/searchbar/person.js @@ -0,0 +1,55 @@ +import action from "./action.js"; +import actions from "./actions.js"; + +export default { + props: [ "res", "actions" ], + components: { + action: action, + actions: actions + }, + emits: [ 'actionexecuted' ], + template: ` +
+ +
+
+ + + + +
+ +
+ + {{ res.gn }} {{ res.sn }} + + +
+ +
+
+
EMail
+ +
+
+ + + +
+
+ +
+ `, + methods: { + }, + computed: { + mailtourl: function() { + return 'mailto:' + this.res.mail; + } + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/raum.js b/public/js/components/searchbar/raum.js new file mode 100644 index 000000000..887820d6c --- /dev/null +++ b/public/js/components/searchbar/raum.js @@ -0,0 +1,51 @@ +import action from "./action.js"; +import actions from "./actions.js"; + +export default { + props: [ "res", "actions" ], + components: { + action: action, + actions: actions + }, + emits: [ 'actionexecuted' ], + template: ` +
+ +
+
+ + + +
+
+ + {{ res.r }} + + +
+ +
+
+
Gebäude
+
{{ res.g }}
+
+
+
Stockwerk
+
{{ res.s }}
+
+
+
Raumnummer
+
{{ res.rn }}
+
+
+ + + +
+
+ +
+ `, + methods: { + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/searchbar.js b/public/js/components/searchbar/searchbar.js new file mode 100644 index 000000000..3626df32a --- /dev/null +++ b/public/js/components/searchbar/searchbar.js @@ -0,0 +1,132 @@ +import person from "./person.js"; +import raum from "./raum.js"; +import employee from "./employee.js"; +import organisationunit from "./organisationunit.js"; + +export default { + props: [ "searchoptions", "searchfunction" ], + data: function() { + return { + searchtimer: null, + showsettings: false, + searchsettings: { + searchstr: '', + types: [] + }, + showresult: false, + searchresult: [], + searching: false, + error: null + }; + }, + components: { + person: person, + raum: raum, + employee: employee, + organisationunit: organisationunit + }, + template: ` +
+
+ + +
+ +
+
+
+ +
+
{{ this.error }}
+
Es wurden keine Ergebnisse gefunden.
+ +
+
+
+ +
+
+ +
+ `, + beforeMount: function() { + this.updateSearchOptions(); + }, + methods: { + updateSearchOptions: function() { + this.searchsettings.types = []; + for( const idx in this.searchoptions.types ) { + this.searchsettings.types.push(this.searchoptions.types[idx]); + } + }, + search: function() { + if( this.searchtimer !== null ) { + clearTimeout(this.searchtimer); + } + if( this.searchsettings.searchstr.length >= 3 ) { + var rect = this.$refs.searchbox.getBoundingClientRect(); + //console.log(window.innerWidth + ' ' + window.innerHeight + ' ' + JSON.stringify(rect)); + this.$refs.result.style.top = Math.floor(rect.bottom + 3) + 'px'; + this.$refs.result.style.right = Math.floor(window.innerWidth - rect.right) + 'px'; + this.$refs.result.style.width = Math.floor(window.innerWidth * 0.75) + 'px'; + this.$refs.result.style.height = Math.floor(window.innerHeight * 0.75) + 'px'; + this.searchtimer = setTimeout( + this.callsearchapi, + 500 + ); + } else { + this.showresult = false; + } + }, + callsearchapi: function() { + var that = this; + this.error = null; + this.searchresult = []; + this.searching = true; + this.showsearchresult(); + this.searchfunction(this.searchsettings) + .then(function(response) { + that.searchresult = response.data.data; + }) + .catch(function(error) { + that.error = 'Bei der Suche ist ein Fehler aufgetreten.' + + ' ' + error.message; + }) + .finally(function() { + that.searching = false; + }); + }, + refreshsearch: function() { + this.search(); + this.togglesettings(); + }, + togglesettings: function() { + this.showsettings = !this.showsettings; + var rect = this.$refs.settingsbutton.getBoundingClientRect(); + //console.log(window.innerWidth + ' ' + window.innerHeight + ' ' + JSON.stringify(rect)); + this.$refs.settings.style.top = Math.floor(rect.bottom + 3) + 'px'; + this.$refs.settings.style.right = Math.floor(window.innerWidth - rect.right) + 'px'; + this.$refs.settings.style.width = Math.floor(window.innerWidth * 0.5) + 'px'; + //this.$refs.settings.style.height = Math.floor(window.innerHeight * 0.5) + 'px'; + }, + hideresult: function(e) { + //window.removeEventListener('click', this.hideresult); + this.showresult = false; + }, + showsearchresult: function() { + if( this.searchsettings.searchstr.length >= 3 ) { + this.showresult = true; + //window.addEventListener('click', this.hideresult); + } + } + } +}; diff --git a/public/js/components/verticalsplit/verticalsplit.js b/public/js/components/verticalsplit/verticalsplit.js new file mode 100644 index 000000000..1a37a786e --- /dev/null +++ b/public/js/components/verticalsplit/verticalsplit.js @@ -0,0 +1,129 @@ +export default { + data: function() { + return { + availHeight: 0, + topheight: 0, + bottomheight: 0, + mousePosY: 0, + resize: false, + vsplitter: null, + vsplitterOffset: 0, + selfOffsetTop: 0 + }; + }, + template: ` +
+
+ +

Top Panel

+
+
+
+
+ + + + + + + + + +
+
+
+ +

Bottom Panel

+
+
+
+ `, + mounted: function() { + this.calcHeights(); + this.trackVerticalSplitterOffsetTop(); + window.addEventListener('resize', this.calcHeights); + }, + updated: function() { + this.trackVerticalSplitterOffsetTop(); + }, + methods: { + calcHeights: function() { + var windowheight = window.innerHeight; + 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 ) { + this.topheight = Math.floor(this.availHeight/2); + } else { + this.topheight = Math.floor( ((((this.topheight * 100) / oldavailHeight) / 100) * this.availHeight) ); + } + this.bottomheight = this.availHeight - this.topheight; + }, + collapseTop: function() { + this.calcHeights(); + this.topheight = 0; + this.bottomheight = this.availHeight; + }, + collapseBottom: function() { + this.calcHeights(); + this.topheight = this.availHeight; + this.bottomheight = 0; + }, + showBoth: function() { + this.topheight = Math.floor(this.availHeight/2); + this.bottomheight = Math.floor(this.availHeight/2); + }, + dragStart: function(e) { + e.preventDefault(); + e.stopPropagation(); + window.addEventListener('mouseup', this.dragEnd); + window.addEventListener('mousemove', this.drag); + this.resize = true; + this.mousePosY = e.clientY; + }, + drag: function(e) { + if( !this.resize ) { + return; + } + e.preventDefault(); + e.stopPropagation(); + var offsetY = e.clientY - this.mousePosY; + this.topheight = this.topheight + offsetY; + if( this.topheight < 0 ) { + this.topheight = 0; + } + if( this.topheight > this.availHeight ) { + this.topheight = this.availHeight; + } + this.bottomheight = this.availHeight - this.topheight; + this.mousePosY = e.clientY; + }, + dragEnd: function(e) { + e.preventDefault(); + e.stopPropagation(); + window.removeEventListener('mousemove', this.drag); + window.removeEventListener('mouseup', this.dragEnd); + this.resize = false; + this.mousePosY = e.clientY; + }, + trackVerticalSplitterOffsetTop: function() { + this.vsplitterOffset = this.$refs.vsplitter.offsetTop; + } + }, + computed: { + topOrBottomClass: function() { + return ((this.vsplitterOffset - this.selfOffsetTop) <= Math.floor(this.availHeight/2)) + ? 'top' + : 'bottom'; + }, + topheightcss: function() { + return this.topheight + 'px'; + }, + bottomheightcss: function() { + return this.bottomheight + 'px'; + } + } +}; \ No newline at end of file