mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-25 16:44:28 +00:00
Merge branch 'master' into feature-35768/Studstatus_Status_Abgebrochen_fuer_Wiederholerdeadline_verwenden
This commit is contained in:
@@ -15,13 +15,17 @@
|
||||
position: absolute;
|
||||
z-index: 9998;
|
||||
background-color: #fff;
|
||||
border: 2px solid #666;
|
||||
border: 1px solid lightgrey;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.searchbar_result {
|
||||
border-bottom: 1px solid #666;
|
||||
border-bottom: 1px solid lightgrey;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -33,6 +37,7 @@
|
||||
.searchbar_icon {
|
||||
grid-column-start: icon;
|
||||
grid-column-end: span 1;
|
||||
margin-right: .75em;
|
||||
}
|
||||
|
||||
.searchbar_data {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
.splitaction {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
width: 42px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
const DEBUG = false;
|
||||
|
||||
// Default veil timeout (milliseconds)
|
||||
const VEIL_TIMEOUT = 1000;
|
||||
const VEIL_TIMEOUT = 5000;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------
|
||||
// Constants
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
const CORE_REST_CLIENT_DEBUG = false;
|
||||
|
||||
// Default timeout (milliseconds)
|
||||
const CORE_REST_CLIENT_TIMEOUT = 1000;
|
||||
const CORE_REST_CLIENT_TIMEOUT = 5000;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------
|
||||
// Constants
|
||||
|
||||
@@ -111,7 +111,8 @@ export default {
|
||||
headerSort: false
|
||||
}, {
|
||||
field: 'studierendenantrag_id',
|
||||
title: '#'
|
||||
title: '#',
|
||||
sorter: 'number'
|
||||
}, {
|
||||
field: 'bezeichnung',
|
||||
title: this.$p.t('lehre', 'studiengang'),
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import {CoreRESTClient} from '../RESTClient.js';
|
||||
import accessibility from "../directives/accessibility.js";
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
accessibility
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
props: {
|
||||
configUrl: String,
|
||||
default: String,
|
||||
modelValue: [String, Number, Boolean, Array, Object, Date, Function, Symbol]
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
current: null,
|
||||
tabs: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentTab() {
|
||||
if (this.tabs[this.current])
|
||||
return this.tabs[this.current];
|
||||
|
||||
return { component: 'div' };
|
||||
},
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(v) {
|
||||
this.$emit('update:modelValue', v);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
CoreRESTClient
|
||||
.get(this.configUrl)
|
||||
.then(result => CoreRESTClient.getData(result.data))
|
||||
.then(result => {
|
||||
const tabs = {};
|
||||
// TODO(chris): check if result is array
|
||||
Object.entries(result).forEach(([key, config]) => {
|
||||
if (!config.component)
|
||||
return console.error('Component missing for ' + key);
|
||||
|
||||
tabs[key] = {
|
||||
component: Vue.markRaw(Vue.defineAsyncComponent(() => import(config.component))),
|
||||
title: config.title || key,
|
||||
config: config.config,
|
||||
key
|
||||
}
|
||||
});
|
||||
if (tabs[this.default])
|
||||
this.current = this.default;
|
||||
else
|
||||
this.current = Object.keys(tabs)[0];
|
||||
this.tabs = tabs;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-tabs d-flex flex-column">
|
||||
<div class="nav nav-tabs">
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="nav-item nav-link"
|
||||
:class="{active: tab.key == current}"
|
||||
@click="current=tab.key"
|
||||
:aria-current="tab.key == current ? 'page' : ''"
|
||||
v-accessibility:tab
|
||||
>
|
||||
{{tab.title}}
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1 1 0%; height: 0%" class="border-bottom border-start border-end overflow-auto p-3">
|
||||
<keep-alive>
|
||||
<component :is="currentTab.component" v-model="value" :config="currentTab.config"></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>`
|
||||
};
|
||||
@@ -521,7 +521,7 @@ export const CoreFilterCmpt = {
|
||||
|
||||
<div class="d-flex flex-row justify-content-between flex-wrap">
|
||||
<div v-if="newBtnShow || reload || $slots.actions" class="d-flex gap-2 align-items-baseline flex-wrap">
|
||||
<button v-if="newBtnShow" class="btn btn-outline-secondary" :class="newBtnClass" :title="newBtnLabel ? undefined : 'New'" :aria-label="newBtnLabel ? undefined : 'New'" @click="$emit('click:new', $event)" :disabled="newBtnDisabled">
|
||||
<button v-if="newBtnShow" class="btn btn-primary" :class="newBtnClass" :title="newBtnLabel ? undefined : 'New'" :aria-label="newBtnLabel ? undefined : 'New'" @click="$emit('click:new', $event)" :disabled="newBtnDisabled">
|
||||
<span class="fa-solid fa-plus" aria-hidden="true"></span>
|
||||
{{ newBtnLabel }}
|
||||
</button>
|
||||
|
||||
@@ -26,8 +26,8 @@ export default {
|
||||
default: []
|
||||
},
|
||||
names: {
|
||||
type: Array,
|
||||
default: []
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
emits: {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import {CoreRESTClient} from '../../RESTClient.js';
|
||||
|
||||
//
|
||||
const CORE_NAVIGATION_CMPT_TIMEOUT = 2000;
|
||||
const CORE_NAVIGATION_CMPT_TIMEOUT = 5000;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
<div class="searchbar_icon">
|
||||
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
|
||||
<img v-if="(typeof res.photo_url !== 'undefined') && (res.photo_url !== null)" :src="res.photo_url"
|
||||
class="rounded-circle" height="100" />
|
||||
class="rounded" style="max-height: 120px; max-width: 90px;" />
|
||||
<i v-else class="fas fa-user-circle fa-5x"></i>
|
||||
</action>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
<div class="searchbar_icon">
|
||||
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
|
||||
<img v-if="(typeof res.foto !== 'undefined') && (res.foto !== null)" :src="res.foto"
|
||||
class="rounded-circle" height="100" />
|
||||
class="rounded" style="max-height: 120px; max-width: 90px;" />
|
||||
<i v-else class="fas fa-user-circle fa-5x"></i>
|
||||
</action>
|
||||
</div>
|
||||
|
||||
@@ -27,13 +27,13 @@ export default {
|
||||
organisationunit: organisationunit
|
||||
},
|
||||
template: `
|
||||
<form ref="searchform" class="d-flex me-3" action="javascript:void(0);"
|
||||
<form ref="searchform" class="d-flex me-3 position-relative" action="javascript:void(0);"
|
||||
@focusin="this.searchfocusin" @focusout="this.searchfocusout">
|
||||
<div class="input-group me-2 bg-white">
|
||||
<input ref="searchbox" @keyup="this.search" @focus="this.showsearchresult"
|
||||
v-model="this.searchsettings.searchstr" class="form-control"
|
||||
type="search" placeholder="Search" aria-label="Search">
|
||||
<button ref="settingsbutton" @click="this.togglesettings" class="btn btn-outline-secondary" type="button" id="search-filter"><i class="fas fa-cog"></i></button>
|
||||
v-model="this.searchsettings.searchstr" class="form-control"
|
||||
type="search" placeholder="Suche..." aria-label="Search">
|
||||
<button ref="settingsbutton" @click="this.togglesettings" class="btn btn-light border-start" type="button" id="search-filter"><i class="fas fa-cog"></i></button>
|
||||
</div>
|
||||
|
||||
<div v-show="this.showresult" ref="result"
|
||||
@@ -46,6 +46,7 @@ export default {
|
||||
<template v-else="" v-for="res in this.searchresult">
|
||||
<person v-if="res.type === 'person'" :res="res" :actions="this.searchoptions.actions.person" @actionexecuted="this.hideresult"></person>
|
||||
<employee v-else-if="res.type === 'mitarbeiter'" :res="res" :actions="this.searchoptions.actions.employee" @actionexecuted="this.hideresult"></employee>
|
||||
<employee v-else-if="res.type === 'mitarbeiter_ohne_zuordnung'" :res="res" :actions="this.searchoptions.actions.employee" @actionexecuted="this.hideresult"></employee>
|
||||
<organisationunit v-else-if="res.type === 'organisationunit'" :res="res" :actions="this.searchoptions.actions.organisationunit" @actionexecuted="this.hideresult"></organisationunit>
|
||||
<raum v-else-if="res.type === 'raum'" :res="res" :actions="this.searchoptions.actions.raum" @actionexecuted="this.hideresult"></raum>
|
||||
<div v-else="">Unbekannter Ergebnistyp: '{{ res.type }}'.</div>
|
||||
@@ -78,16 +79,13 @@ export default {
|
||||
calcSearchResultExtent: function() {
|
||||
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.$refs.result.style.height = Math.floor(window.innerHeight * 0.80) + 'px';
|
||||
},
|
||||
search: function() {
|
||||
if( this.searchtimer !== null ) {
|
||||
clearTimeout(this.searchtimer);
|
||||
}
|
||||
if( this.searchsettings.searchstr.length >= 3 ) {
|
||||
if( this.searchsettings.searchstr.length >= 2 ) {
|
||||
this.calcSearchResultExtent();
|
||||
this.searchtimer = setTimeout(
|
||||
this.callsearchapi,
|
||||
|
||||
@@ -23,13 +23,13 @@ export default {
|
||||
:class="this.topOrBottomClass" @mousedown="this.dragStart">
|
||||
<div class="splitactions" :class="this.topOrBottomClass">
|
||||
<span @click="this.collapseTop" class="splitaction">
|
||||
<i class="fas fa-angle-up"></i>
|
||||
<i class="fas fa-angle-up text-muted"></i>
|
||||
</span>
|
||||
<span @dblclick="this.showBoth" class="splitaction resize">
|
||||
<i class="fas fa-grip-horizontal"></i>
|
||||
<i class="fas fa-grip-lines text-muted"></i>
|
||||
</span>
|
||||
<span @click="this.collapseBottom" class="splitaction">
|
||||
<i class="fas fa-angle-down"></i>
|
||||
<i class="fas fa-angle-down text-muted"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
$path = "../../../vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js";
|
||||
|
||||
if(file_exists("../../../vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js"))
|
||||
if(file_exists($path))
|
||||
{
|
||||
header('Content-Type: application/javascript');
|
||||
echo file_get_contents("../../../vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js");
|
||||
echo file_get_contents($path);
|
||||
echo "export default VueDatePicker";
|
||||
}
|
||||
else
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
export default {
|
||||
created(el, binding) {
|
||||
switch (binding.arg) {
|
||||
case 'tab':
|
||||
el.style.cursor = 'pointer';
|
||||
const [prev, next] = binding.modifiers.vertical ? ['ArrowUp', 'ArrowDown'] : ['ArrowLeft', 'ArrowRight'];
|
||||
el.addEventListener('click', () => {
|
||||
let act = el.parentNode.querySelector('[tabindex="0"]');
|
||||
if (act)
|
||||
act.tabIndex = -1;
|
||||
el.tabIndex = 0;
|
||||
});
|
||||
el.addEventListener('focus', () => {
|
||||
el.ariaSelected = "true";
|
||||
});
|
||||
el.addEventListener('blur', () => {
|
||||
el.ariaSelected = "false";
|
||||
});
|
||||
el.addEventListener('keydown', e => {
|
||||
switch (e.code) {
|
||||
case prev:
|
||||
if (el.previousSibling?.tabIndex !== undefined) {
|
||||
el.previousSibling.tabIndex = 0;
|
||||
el.tabIndex = -1;
|
||||
el.previousSibling.focus();
|
||||
}
|
||||
break;
|
||||
case next:
|
||||
if (el.nextSibling?.tabIndex !== undefined) {
|
||||
el.nextSibling.tabIndex = 0;
|
||||
el.tabIndex = -1;
|
||||
el.nextSibling.focus();
|
||||
}
|
||||
break;
|
||||
case 'Enter':
|
||||
el.click();
|
||||
break;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
mounted(el, binding) {
|
||||
switch (binding.arg) {
|
||||
case 'tab':
|
||||
let activetab = -1;
|
||||
Array.from(el.parentNode.children).forEach((node, index) => {
|
||||
node.ariaSetSize = el.parentNode.children.length;
|
||||
node.ariaPosInSet = index;
|
||||
if (node.tabIndex === 0)
|
||||
activetab = index;
|
||||
});
|
||||
if (activetab == -1) {
|
||||
el.tabIndex = 0;
|
||||
} else if (el.classList.contains('active')) {
|
||||
el.parentNode.children[activetab].tabIndex = -1;
|
||||
el.tabIndex = 0;
|
||||
} else {
|
||||
el.tabIndex = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
beforeUnmount(el, binding) {
|
||||
switch (binding.arg) {
|
||||
case 'tab':
|
||||
if (el.tabIndex === 0) {
|
||||
if (el.previousSibling?.tabIndex !== undefined)
|
||||
el.previousSibling.tabIndex = 0;
|
||||
else if (el.nextSibling?.tabIndex !== undefined)
|
||||
el.nextSibling.tabIndex = 0;
|
||||
}
|
||||
const pos = parseInt(el.ariaPosInSet);
|
||||
Array.from(el.parentNode.children).forEach((node, index) => {
|
||||
node.ariaSetSize = el.parentNode.children.length;
|
||||
if (index > pos)
|
||||
node.ariaPosInSet = index-1;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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/>.
|
||||
*
|
||||
* @usage:
|
||||
* Preperations:
|
||||
* Be sure to have PrimeVue loaded with the toast and confirmdialog
|
||||
* components as primevue variable
|
||||
* Install:
|
||||
* Import this Plugin and install it with the app.use() function.
|
||||
* Use:
|
||||
* In your component you can call now the global property $fhcAlert
|
||||
* which has the following functions:
|
||||
*
|
||||
* alertSuccess
|
||||
* ------------
|
||||
* Displays a success message
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertInfo
|
||||
* ---------
|
||||
* Displays an info message
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertWarning
|
||||
* ------------
|
||||
* Displays a warning
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertError
|
||||
* ----------
|
||||
* Displays an error
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertSystemError
|
||||
* ----------------
|
||||
* Displays an alert with the error details and a button to mail
|
||||
* the error to the Support Team
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* confirmDelete
|
||||
* -------------
|
||||
* Displays a confirmation dialog and returns a Promise which resolves
|
||||
* with true or false depending und the pressed button.
|
||||
* @return Promise
|
||||
*
|
||||
* alertDefault
|
||||
* ------------
|
||||
* Displays an alert
|
||||
* @param string severity can be 'success', 'info', 'warning', 'error'
|
||||
* @param string title
|
||||
* @param string message
|
||||
* @param boolean sticky (optional) defaults to false
|
||||
* @return void
|
||||
*
|
||||
* alertMultiple
|
||||
* -------------
|
||||
* Displays multiple alerts
|
||||
* @param array messageArray
|
||||
* @param string severity (optional) defaults to 'info'
|
||||
* @param string title (optional) defaults to 'Info'
|
||||
* @param boolean sticky (optional) defaults to false
|
||||
* @return void
|
||||
*
|
||||
* handleSystemError
|
||||
* -----------------
|
||||
* Automatiticly determine how to display an system error and display it.
|
||||
* This would be used in a catch block of an ajax call.
|
||||
* @param mixed error
|
||||
* @return void
|
||||
*
|
||||
* handleSystemMessage
|
||||
* -------------------
|
||||
* Automatiticly determine how to display a message and display it.
|
||||
* @param mixed message
|
||||
* @return void
|
||||
*/
|
||||
import PvConfig from "../../../index.ci.php/public/js/components/primevue/config/config.esm.min.js";
|
||||
import PvToast from "../../../index.ci.php/public/js/components/primevue/toast/toast.esm.min.js";
|
||||
import PvConfirm from "../../../index.ci.php/public/js/components/primevue/confirmdialog/confirmdialog.esm.min.js";
|
||||
import PvConfirmationService from "../../../index.ci.php/public/js/components/primevue/confirmationservice/confirmationservice.esm.min.js";
|
||||
|
||||
import {CoreRESTClient} from '../RESTClient.js';
|
||||
|
||||
const helperAppContainer = document.createElement('div');
|
||||
|
||||
const helperApp = Vue.createApp({
|
||||
components: {
|
||||
PvToast,
|
||||
PvConfirm
|
||||
},
|
||||
methods: {
|
||||
mailToUrl(slotProps) {
|
||||
let mailTo = '[email protected]'; // TODO domain anpassen
|
||||
let subject = 'Meldung%20Systemfehler';
|
||||
let body = `
|
||||
Danke, dass Sie uns den Fehler melden. %0D%0A %0D%0A
|
||||
Bitte beschreiben Sie uns ausführlich das Problem.%0D%0A
|
||||
Bsp: Ich habe X ausgewählt und Y angelegt. Beim Speichern erhielt ich die Fehlermeldung. [Optional: Ich habe den Browser Z verwendet.]%0D%0A
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
|
||||
PROBLEM: ... %0D%0A %0D%0A %0D%0A
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
|
||||
Fehler URL: ` + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method + `%0D%0A
|
||||
Fehler Meldung: ` + slotProps.message.detail + `%0D%0A
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A %0D%0A
|
||||
Wir kümmern uns um eine rasche Behebung des Problems!`
|
||||
|
||||
return "mailto:" + mailTo + "?subject=" + subject + "&body=" + body;
|
||||
},
|
||||
openMessagecard(e) {
|
||||
bootstrap.Collapse.getOrCreateInstance(e.target.getAttribute('href')).toggle();
|
||||
}
|
||||
},
|
||||
unmounted() {
|
||||
helperAppContainer.parentElement.removeChild(helperAppContainer);
|
||||
},
|
||||
template: `
|
||||
<pv-toast ref="toast" base-z-index="99999"></pv-toast>
|
||||
<pv-toast ref="alert" base-z-index="99999" position="center">
|
||||
<template #message="slotProps">
|
||||
<i class="fa fa-circle-exclamation fa-2xl mt-3"></i>
|
||||
<div class="p-toast-message-text">
|
||||
<span class="p-toast-summary">{{slotProps.message.summary}}</span>
|
||||
<div class="p-toast-detail my-3">Sorry! Ein interner technischer Fehler ist aufgetreten.</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a
|
||||
class="align-bottom flex-fill me-2"
|
||||
data-bs-toggle="collapse"
|
||||
:href="'#fhcAlertCollapseMessageCard' + slotProps.message.id"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'fhcAlertCollapseMessageCard' + slotProps.message.id"
|
||||
@click="openMessagecard"
|
||||
>
|
||||
Fehler anzeigen
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary flex-fill"
|
||||
target="_blank"
|
||||
:href="mailToUrl(slotProps)"
|
||||
>
|
||||
Fehler melden
|
||||
</a>
|
||||
</div>
|
||||
<div ref="messageCard" :id="'fhcAlertCollapseMessageCard' + slotProps.message.id" class="collapse mt-3">
|
||||
<div class="card card-body text-body small" style="white-space: pre-wrap">
|
||||
{{slotProps.message.detail}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</pv-toast>
|
||||
<pv-confirm group="fhcAlertConfirm"></pv-confirm>`
|
||||
});
|
||||
|
||||
helperApp.use(PvConfig);
|
||||
helperApp.use(PvConfirmationService);
|
||||
|
||||
const helperAppInstance = helperApp.mount(helperAppContainer);
|
||||
|
||||
document.body.appendChild(helperAppContainer);
|
||||
|
||||
|
||||
export default {
|
||||
install: (app, options) => {
|
||||
const $fhcAlert = {
|
||||
alertSuccess(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertSuccess);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'success', summary: 'Info', detail: message, life: 1000});
|
||||
},
|
||||
alertInfo(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertInfo);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'info', summary: 'Info', detail: message, life: 3000 });
|
||||
},
|
||||
alertWarning(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertWarning);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'warn', summary: 'Achtung', detail: message});
|
||||
},
|
||||
alertError(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertError);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'error', summary: 'Achtung', detail: message });
|
||||
},
|
||||
alertSystemError(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertSystemError);
|
||||
helperAppInstance.$refs.alert.add({ severity: 'error', summary: 'Systemfehler', detail: message});
|
||||
},
|
||||
confirmDelete() {
|
||||
return new Promise((resolve, reject) => {
|
||||
helperAppInstance.$confirm.require({
|
||||
group: 'fhcAlertConfirm',
|
||||
header: 'Achtung',
|
||||
message: 'Möchten Sie sicher löschen?',
|
||||
acceptLabel: 'Löschen',
|
||||
acceptClass: 'btn btn-danger',
|
||||
rejectLabel: 'Abbrechen',
|
||||
rejectClass: 'btn btn-outline-secondary',
|
||||
accept() {
|
||||
resolve(true);
|
||||
},
|
||||
reject() {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
alertDefault(severity, title, message, sticky = false) {
|
||||
let options = { severity: severity, summary: title, detail: message};
|
||||
|
||||
if (!sticky)
|
||||
options.life = 3000;
|
||||
|
||||
helperAppInstance.$refs.toast.add(options);
|
||||
},
|
||||
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false){
|
||||
// Check, if array has only string values
|
||||
if (messageArray.every(message => typeof message === 'string')) {
|
||||
messageArray.every(message => this.alertDefault(severity, title, message, sticky));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
handleSystemError(error) {
|
||||
// Error is string
|
||||
if (typeof error === 'string')
|
||||
return $fhcAlert.alertSystemError(error);
|
||||
|
||||
// Error is array of strings
|
||||
if (Array.isArray(error) && error.every(err => typeof err === 'string'))
|
||||
return error.every($fhcAlert.alertSystemError);
|
||||
|
||||
// Error is object
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
let errMsg = '';
|
||||
|
||||
if (error.hasOwnProperty('response') && error.response?.data?.retval)
|
||||
errMsg += 'Error Message: ' + (error.response.data.retval.message || error.response.data.retval) + '\r\n';
|
||||
else if (error.hasOwnProperty('message'))
|
||||
errMsg += 'Error Message: ' + error.message.toUpperCase() + '\r\n';
|
||||
|
||||
if (error.hasOwnProperty('config') && error.config.hasOwnProperty('url'))
|
||||
errMsg += 'Error ConfigURL: ' + error.config.url + '\r\n';
|
||||
|
||||
if (error.hasOwnProperty('stack'))
|
||||
errMsg += 'Error Stack: ' + error.stack + '\r\n';
|
||||
|
||||
// Fallback object error message
|
||||
if (errMsg == '')
|
||||
errMsg = 'Error Message: ' + JSON.stringify(error) + '\r\n';
|
||||
|
||||
errMsg += 'Error Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method;
|
||||
|
||||
return $fhcAlert.alertSystemError(errMsg);
|
||||
}
|
||||
|
||||
// Fallback
|
||||
$fhcAlert.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
|
||||
},
|
||||
handleSystemMessage(message) {
|
||||
// Message is string
|
||||
if (typeof message === 'string')
|
||||
return fhcerror.alertWarning(message);
|
||||
|
||||
// Message is array of strings
|
||||
if (Array.isArray(message)) {
|
||||
// If Array has only Strings
|
||||
if (message.every(msg => typeof msg === 'string'))
|
||||
return message.every(fhcerror.alertWarning);
|
||||
|
||||
// If Array has only Objects
|
||||
if (message.every(msg => typeof msg === 'object') && msg !== null) {
|
||||
return message.every(msg => {
|
||||
if (msg.hasOwnProperty('data') && msg.data.hasOwnProperty('retval')) {
|
||||
fhcerror.alertWarning(JSON.stringify(msg.data.retval));
|
||||
} else {
|
||||
fhcerror.alertSystemError(JSON.stringify(msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Message is Object with data property
|
||||
if (typeof message === 'object' && message !== null){
|
||||
if (message.hasOwnProperty('data') && message.data.hasOwnProperty('retval')) {
|
||||
// NOTE(chris): changed: alertSystemError => alertWarning
|
||||
fhcerror.alertWarning(JSON.stringify(message.data.retval));
|
||||
} else {
|
||||
fhcerror.alertSystemError(JSON.stringify(message));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
fhcerror.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
|
||||
},
|
||||
resetFormValidation(form) {
|
||||
const event = new Event('fhc-form-reset');
|
||||
form.querySelectorAll(['[data-fhc-form-validate],[data-fhc-form-error]']).forEach(el => el.dispatchEvent(event));
|
||||
/*const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
|
||||
if (alert) {
|
||||
alert.innerHTML = '';
|
||||
alert.classList.add('d-none');
|
||||
}
|
||||
form.querySelectorAll('.invalid-feedback').forEach(n => n.remove());
|
||||
form.querySelectorAll('.is-invalid').forEach(n => n.classList.remove('is-invalid'));
|
||||
form.querySelectorAll('.is-valid').forEach(n => n.classList.remove('is-valid'));*/
|
||||
},
|
||||
handleFormValidation(error, form) {
|
||||
if (form === undefined) {
|
||||
if (error && error.nodeType === Node.ELEMENT_NODE)
|
||||
return err => $fhcAlert.handleFormValidation(err, error);
|
||||
} else {
|
||||
if (error?.response?.status == 400) {
|
||||
let errors = CoreRESTClient.getError(error.response.data);
|
||||
if (typeof errors !== "object")
|
||||
errors = error.response.data;
|
||||
|
||||
// NOTE(chris): reset form validation
|
||||
$fhcAlert.resetFormValidation(form);
|
||||
|
||||
// NOTE(chris): set form input validation
|
||||
const notFound = Object.entries(errors).filter(([key, detail]) => {
|
||||
const input = form.querySelector('[data-fhc-form-validate="' + key + '"]');
|
||||
if (!input)
|
||||
return true;
|
||||
|
||||
input.dispatchEvent(new CustomEvent('fhc-form-invalidate', {detail}));
|
||||
|
||||
/*const input = form.querySelector('[name="' + key + '"]');
|
||||
if (!input)
|
||||
return true;
|
||||
input.classList.add('is-invalid');
|
||||
const feedback = document.createElement('div');
|
||||
feedback.classList.add('invalid-feedback');
|
||||
feedback.innerHTML = detail;
|
||||
input.after(feedback);*/
|
||||
return false;
|
||||
}).map(arr => arr[1]);
|
||||
|
||||
|
||||
//const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
|
||||
const alert = form.querySelector('[data-fhc-form-error]');
|
||||
if (alert && notFound.length) {
|
||||
alert.dispatchEvent(new CustomEvent('fhc-form-error', {detail: notFound}));
|
||||
/*notFound.forEach(txt => {
|
||||
const p = document.createElement('p');
|
||||
p.innerHTML = txt;
|
||||
alert.append(p);
|
||||
});
|
||||
|
||||
if (notFound.length) {
|
||||
alert.lastChild.classList.add('mb-0');
|
||||
alert.classList.remove('d-none');
|
||||
}*/
|
||||
} else {
|
||||
notFound.forEach($fhcAlert.alertError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (error?.response?.status == 400) {
|
||||
let errors = CoreRESTClient.getError(error.response.data);
|
||||
$fhcAlert.alertError((typeof errors === 'object') ? Object.values(errors) : errors);
|
||||
} else {
|
||||
$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
app.config.globalProperties.$fhcAlert = $fhcAlert;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user