mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-25 08:34:29 +00:00
Merge branch 'master' into feature-61232/Studierendenverwaltung_Karteireiter_Projektarbeit_portieren
This commit is contained in:
@@ -28,3 +28,13 @@ textarea[name="anmerkung"] {
|
||||
content: '\f073';
|
||||
color: #f3c541;
|
||||
}
|
||||
|
||||
|
||||
.modal-dialog.modal-xxl {
|
||||
max-width: 95% !important;
|
||||
}
|
||||
|
||||
.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after
|
||||
{
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
+153
-1
@@ -1 +1,153 @@
|
||||
@import '../../vendor/olifolkerd/tabulator6/dist/css/tabulator_simple.min.css';
|
||||
@import '../../vendor/olifolkerd/tabulator6/dist/css/tabulator_simple.min.css';
|
||||
.tabulator-row {
|
||||
border-bottom: none;
|
||||
}
|
||||
.tabulator-row .tabulator-frozen,
|
||||
.tabulator-row .tabulator-cell {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
.tabulator-row.tabulator-row-even {
|
||||
background-color: transparent;
|
||||
}
|
||||
.tabulator-headers .tabulator-frozen,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-frozen,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-cell {
|
||||
background-color: #fff;
|
||||
}
|
||||
.tabulator-row.tabulator-row-even .tabulator-frozen,
|
||||
.tabulator-row.tabulator-row-even .tabulator-cell {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
.tabulator-row.tabulator-selectable:hover .tabulator-frozen,
|
||||
.tabulator-row.tabulator-selectable:hover .tabulator-cell {
|
||||
background-color: #ececec;
|
||||
}
|
||||
.tabulator-row.tabulator-selected .tabulator-frozen,
|
||||
.tabulator-row.tabulator-selected .tabulator-cell {
|
||||
background-color: #9abcea;
|
||||
}
|
||||
.tabulator-row.tabulator-selected:hover .tabulator-frozen,
|
||||
.tabulator-row.tabulator-selected:hover .tabulator-cell {
|
||||
background-color: #769bcc;
|
||||
}
|
||||
.tabulator .tabulator-col-resize-handle:last-of-type {
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
/* classes for rows that are not selectable in the tabulator, except for rows that are used for calculation */
|
||||
.tabulator-row.tabulator-unselectable:not(.tabulator-calcs) {
|
||||
color: #adb5bd !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.tabulator-row.tabulator-unselectable a {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
/* using bootstrap background classes to style the background color of tabulator rows */
|
||||
/* bg-warning */
|
||||
/* odd-rows */
|
||||
.tabulator-headers .tabulator-frozen.bg-warning,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-frozen.bg-warning,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-cell.bg-warning {
|
||||
background-color: #fcf8e3;
|
||||
|
||||
}
|
||||
/* even-rows */
|
||||
.tabulator-headers .tabulator-frozen.bg-warning,
|
||||
.tabulator-row.tabulator-row-even .tabulator-frozen.bg-warning,
|
||||
.tabulator-row.tabulator-row-even .tabulator-cell.bg-warning {
|
||||
background-color: #fcf8e3;
|
||||
|
||||
}
|
||||
|
||||
/* bg-success */
|
||||
/* odd-rows */
|
||||
.tabulator-headers .tabulator-frozen.bg-success,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-frozen.bg-success,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-cell.bg-success {
|
||||
background-color: #dff0d8;
|
||||
|
||||
}
|
||||
/* even-rows */
|
||||
.tabulator-headers .tabulator-frozen.bg-success,
|
||||
.tabulator-row.tabulator-row-even .tabulator-frozen.bg-success,
|
||||
.tabulator-row.tabulator-row-even .tabulator-cell.bg-success {
|
||||
background-color: #dff0d8;
|
||||
|
||||
}
|
||||
|
||||
/* bg-info */
|
||||
/* odd-rows */
|
||||
.tabulator-headers .tabulator-frozen.bg-info,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-frozen.bg-info,
|
||||
.tabulator-row.tabulator-row-odd .tabulator-cell.bg-info {
|
||||
background-color: #d9edf7;
|
||||
|
||||
}
|
||||
/* even-rows */
|
||||
.tabulator-headers .tabulator-frozen.bg-info,
|
||||
.tabulator-row.tabulator-row-even .tabulator-frozen.bg-info,
|
||||
.tabulator-row.tabulator-row-even .tabulator-cell.bg-info {
|
||||
background-color: #d9edf7;
|
||||
|
||||
}
|
||||
|
||||
/* special bootstrap5 styling for tableWidget and their accordion-item ::after content */
|
||||
.accordion-button::after{
|
||||
content:none !important;
|
||||
}
|
||||
|
||||
|
||||
.tabulator {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tabulator-initialfontsize .tabulator {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tabulator-row.tabulator-unselectable .tabulator-cell {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.tabulator-tooltip {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.tabulator-row.tabulator-unselectable:not(.tabulator-calcs) {
|
||||
color: #777 !important;
|
||||
}
|
||||
|
||||
.tabulator-cell .btn {
|
||||
padding: 0 .375rem;
|
||||
font-size: calc(1rem - 2px / 1.5); /* substract border (2 x 1px) modified by the line-height (1.5) */
|
||||
border-radius: .2rem;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.tabulator-row.tabulator-selectable:focus {
|
||||
box-shadow: 0 0 0 .24rem rgba(13,110,253,.25);
|
||||
z-index: 1;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.btn-select-col-selected
|
||||
{
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.accordion-item-dark, .accordion-item-dark:focus{
|
||||
background-color:#CED4DA !important;
|
||||
border-color:#ADB5BD !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make keyboard-focused list items look like the mouse-hovered list items
|
||||
*/
|
||||
.tabulator-edit-list .tabulator-edit-list-item.focused {
|
||||
color: #fff;
|
||||
background: #1d68cd;
|
||||
}
|
||||
|
||||
@@ -203,3 +203,14 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.hiddenWidget{
|
||||
background: var(--fhc-disabled-background);
|
||||
opacity: 40%;
|
||||
}
|
||||
|
||||
.hiddenWidget .card,
|
||||
.hiddenWidget .card-body,
|
||||
.hiddenWidget .card-body *{
|
||||
background: inherit !important;
|
||||
}
|
||||
|
||||
@@ -3973,6 +3973,10 @@
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.p-tabview-panel .btn-close,
|
||||
.p-tabview-panel .carousel-indicators [data-bs-target] {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.p-toolbar {
|
||||
background: #efefef;
|
||||
|
||||
@@ -89,7 +89,8 @@
|
||||
--fhc-link: var(--fhc-blue-10);
|
||||
|
||||
/* disabled */
|
||||
--fhc-disabled: var(--fhc-gray-10);
|
||||
--fhc-disabled: var(--fhc-gray-90);
|
||||
--fhc-disabled-background: var(--fhc-gray-30);
|
||||
|
||||
/* status colors */
|
||||
--fhc-danger: var(--fhc-red-10);
|
||||
@@ -135,6 +136,7 @@
|
||||
|
||||
/* disabled */
|
||||
--fhc-disabled: var(--fhc-gray-10);
|
||||
--fhc-disabled-background: var(--fhc-gray-120);
|
||||
|
||||
/* status colors */
|
||||
--fhc-danger: var(--fhc-red-10);
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAllBetriebsmittel(type, id) {
|
||||
getAllBetriebsmittel(type, id, betriebsmitteltypes) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getAllBetriebsmittel/' + type + '/' + id
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getAllBetriebsmittel/' + type + '/' + id,
|
||||
params: {
|
||||
betriebsmitteltypes
|
||||
}
|
||||
};
|
||||
},
|
||||
addNewBetriebsmittel(person_id, params) {
|
||||
@@ -48,10 +51,13 @@ export default {
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/deleteBetriebsmittel/' + betriebsmittelperson_id
|
||||
};
|
||||
},
|
||||
getTypenBetriebsmittel() {
|
||||
getTypenBetriebsmittel(betriebsmitteltypes) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getTypenBetriebsmittel/'
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getTypenBetriebsmittel/',
|
||||
params: {
|
||||
betriebsmitteltypes
|
||||
}
|
||||
};
|
||||
},
|
||||
loadInventarliste(query) {
|
||||
|
||||
@@ -83,5 +83,11 @@ export default {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/notiz/notizPerson/isBerechtigt/'
|
||||
};
|
||||
},
|
||||
getCountNotes(person_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/notiz/notizPerson/getCountNotes/' + person_id
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,5 +22,19 @@ export default {
|
||||
url: '/api/frontend/v1/Ort/ContentID',
|
||||
params: { ort_kurzbz: ort_kurbz }
|
||||
};
|
||||
},
|
||||
getRooms(datum, von, bis, typ, personenanzahl = 0) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Ort/getRooms',
|
||||
params: { datum, von, bis, typ, personenanzahl }
|
||||
};
|
||||
},
|
||||
getRoomTypes() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Ort/getTypes',
|
||||
params: { }
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -68,6 +68,13 @@ export default {
|
||||
params: dms
|
||||
};
|
||||
},
|
||||
getProfilUpdateWithPermission(filter) {
|
||||
const url_filter = (filter !== '') ? '/' + encodeURIComponent(filter) : '';
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/ProfilUpdate/getProfilUpdateWithPermission' + url_filter
|
||||
};
|
||||
},
|
||||
getProfilRequestFiles(requestID) {
|
||||
return {
|
||||
method: 'get',
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (C) 2025 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/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
info(app, path) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/RouteInfo/info',
|
||||
params: {
|
||||
app: app,
|
||||
path: path
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.j
|
||||
import Studium from "../../components/Cis/Studium/Studium.js";
|
||||
|
||||
import ApiRenderers from '../../api/factory/renderers.js';
|
||||
import ApiRouteInfo from '../../api/factory/routeinfo.js';
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
@@ -138,10 +139,10 @@ const router = VueRouter.createRouter({
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/MyLv`,
|
||||
path: `/Cis/MyLv/:studiensemester?`,
|
||||
name: 'MyLv',
|
||||
component: MylvStudent,
|
||||
props: true
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: `/Cis/MyLv/Info/:studien_semester/:lehrveranstaltung_id`,
|
||||
@@ -319,6 +320,7 @@ const app = Vue.createApp({
|
||||
|
||||
// kind of a bandaid for bad css on some pages to avoid horizontal scroll
|
||||
setScrollbarWidth();
|
||||
|
||||
app.use(router);
|
||||
app.use(primevue.config.default, {
|
||||
zIndex: {
|
||||
@@ -331,3 +333,7 @@ app.use(PluginsPhrasen);
|
||||
app.use(Theme);
|
||||
app.directive('contrast', contrast);
|
||||
app.mount('#fhccontent');
|
||||
|
||||
router.afterEach((to, from, failure) => {
|
||||
app.config.globalProperties.$api.call(ApiRouteInfo.info('cis4', to.fullPath));
|
||||
});
|
||||
@@ -29,14 +29,24 @@ export default {
|
||||
uid: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
/** List of types to allow for creation */
|
||||
betriebsmittelTypes: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* If true: only show the types specified in 'betriebsmittelTypes'.
|
||||
* If false: show all available types.
|
||||
*/
|
||||
filterByProvidedTypes: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabulatorOptions: {
|
||||
ajaxURL: 'dummy',
|
||||
ajaxRequestFunc: () => this.$api.call(
|
||||
this.endpoint.getAllBetriebsmittel(this.typeId, this.id)
|
||||
this.endpoint.getAllBetriebsmittel(this.typeId, this.id, (this.filterByProvidedTypes ? this.betriebsmittelTypes : null))
|
||||
),
|
||||
ajaxResponse: (url, params, response) => response.data,
|
||||
columns: [
|
||||
@@ -294,7 +304,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
return this.$api
|
||||
.call(this.endpoint.getTypenBetriebsmittel())
|
||||
.call(this.endpoint.getTypenBetriebsmittel(this.betriebsmittelTypes))
|
||||
.then(result => {
|
||||
this.listBetriebsmitteltyp = result.data;
|
||||
})
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
export default {
|
||||
name: 'BootstrapModal',
|
||||
data: () => ({
|
||||
modal: null
|
||||
modal: null,
|
||||
fullscreen: false
|
||||
}),
|
||||
props: {
|
||||
backdrop: {
|
||||
@@ -34,6 +35,10 @@ export default {
|
||||
footerClass: {
|
||||
type: [String,Array,Object],
|
||||
default: ''
|
||||
},
|
||||
allowFullscreenExpand: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
@@ -58,6 +63,9 @@ export default {
|
||||
},
|
||||
toggle() {
|
||||
return this.modal.toggle();
|
||||
},
|
||||
toggleFullscreen() {
|
||||
this.fullscreen = !this.fullscreen
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -122,13 +130,18 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
template: `<div ref="modal" class="bootstrap-modal modal" tabindex="-1" @[\`hide.bs.modal\`]="$emit('hideBsModal')" @[\`hidden.bs.modal\`]="$emit('hiddenBsModal')" @[\`hidePrevented.bs.modal\`]="$emit('hidePreventedBsModal')" @[\`show.bs.modal\`]="$emit('showBsModal')" >
|
||||
<div class="modal-dialog" :class="dialogClass">
|
||||
template: `<div ref="modal" class="bootstrap-modal modal" tabindex="-1" @[\`hide.bs.modal\`]="$emit('hideBsModal')" @[\`hidden.bs.modal\`]="$emit('hiddenBsModal')" @[\`hidePrevented.bs.modal\`]="$emit('hidePreventedBsModal')" @[\`show.bs.modal\`]="$emit('showBsModal')" @[\`shown.bs.modal\`]="$emit('shownBsModal')">
|
||||
<div class="modal-dialog" :class="fullscreen ? 'modal-fullscreen' : dialogClass">
|
||||
<div class="modal-content">
|
||||
<div v-if="$slots.title" class="modal-header" :class="headerClass">
|
||||
<h5 class="modal-title"><slot name="title"/></h5>
|
||||
<slot name="popoutButton"></slot>
|
||||
<button v-if="!noCloseBtn" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="d-flex align-items-center ms-auto">
|
||||
<button type="button" class="btn ms-auto" style="filter: invert(1)" v-if="allowFullscreenExpand" @click="toggleFullscreen">
|
||||
<i v-if="!fullscreen" class="fa-solid fa-expand"></i>
|
||||
<i v-else class="fa-solid fa-compress"></i>
|
||||
</button>
|
||||
<button v-if="!noCloseBtn" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<slot name="modal-header-content"></slot>
|
||||
</div>
|
||||
<div class="modal-body" :class="bodyClass">
|
||||
|
||||
@@ -44,11 +44,16 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -47,10 +47,15 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -57,65 +57,70 @@ export default {
|
||||
p.style.setProperty('white-space', 'normal');
|
||||
p.style.setProperty('max-width', '400px');
|
||||
})
|
||||
},
|
||||
prepareContent() {
|
||||
// replaces the tablesorter with the tabulator
|
||||
let tables = Array.from(document.getElementsByClassName("tablesorter"));
|
||||
|
||||
tables.forEach((table, index) => {
|
||||
this.sanitizeLegacyTables(table)
|
||||
|
||||
new Tabulator(table, {
|
||||
index: index,
|
||||
layout: "fitDataFill",
|
||||
|
||||
columnDefaults: {
|
||||
formatter: "html",
|
||||
resizable: true,
|
||||
minWidth: "100px"
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
document.querySelectorAll("#cms [data-confirm]").forEach((el) => {
|
||||
el.addEventListener("click", (evt) => {
|
||||
evt.preventDefault();
|
||||
BsConfirm.popup(el.dataset.confirm)
|
||||
.then(() => {
|
||||
Axios.get(el.href)
|
||||
.then((res) => {
|
||||
// TODO(chris): check for success then show message and/or reload
|
||||
location = location;
|
||||
})
|
||||
.catch((err) => console.error("ERROR:", err));
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
});
|
||||
document.querySelectorAll("#cms [data-href]").forEach((el) => {
|
||||
el.href = el.dataset.href.replace(
|
||||
/^ROOT\//,
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[href]").forEach((element) => {
|
||||
let orignal_href = element.getAttribute("href");
|
||||
let new_href = replaceRelativeLegacyLink(orignal_href);
|
||||
element.href = new_href;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[style*=background-color]").forEach((element) => {
|
||||
if (element.style.backgroundColor == "rgb(255, 255, 255)"){
|
||||
element.style.backgroundColor = "var(--fhc-background)";
|
||||
}
|
||||
if(element.querySelector("*[style*=background-color]")){
|
||||
element.style.backgroundColor = "var(--fhc-tertiary)";
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
// replaces the tablesorter with the tabulator
|
||||
let tables = Array.from(document.getElementsByClassName("tablesorter"));
|
||||
|
||||
tables.forEach((table, index) => {
|
||||
this.sanitizeLegacyTables(table)
|
||||
|
||||
new Tabulator(table, {
|
||||
index: index,
|
||||
layout: "fitDataFill",
|
||||
|
||||
columnDefaults: {
|
||||
formatter: "html",
|
||||
resizable: true,
|
||||
minWidth: "100px"
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
document.querySelectorAll("#cms [data-confirm]").forEach((el) => {
|
||||
el.addEventListener("click", (evt) => {
|
||||
evt.preventDefault();
|
||||
BsConfirm.popup(el.dataset.confirm)
|
||||
.then(() => {
|
||||
Axios.get(el.href)
|
||||
.then((res) => {
|
||||
// TODO(chris): check for success then show message and/or reload
|
||||
location = location;
|
||||
})
|
||||
.catch((err) => console.error("ERROR:", err));
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
});
|
||||
document.querySelectorAll("#cms [data-href]").forEach((el) => {
|
||||
el.href = el.dataset.href.replace(
|
||||
/^ROOT\//,
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[href]").forEach((element) => {
|
||||
let orignal_href = element.getAttribute("href");
|
||||
let new_href = replaceRelativeLegacyLink(orignal_href);
|
||||
element.href = new_href;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[style*=background-color]").forEach((element) => {
|
||||
if (element.style.backgroundColor == "rgb(255, 255, 255)"){
|
||||
element.style.backgroundColor = "var(--fhc-background)";
|
||||
}
|
||||
if(element.querySelector("*[style*=background-color]")){
|
||||
element.style.backgroundColor = "var(--fhc-tertiary)";
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
updated() {
|
||||
this.prepareContent();
|
||||
},
|
||||
mounted(){
|
||||
this.prepareContent();
|
||||
},
|
||||
template: /*html*/ `
|
||||
<!-- div that contains the content -->
|
||||
<div v-if="content" class="container" style="max-width: 100%;"><div class="row"><div class="col">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { replaceRelativeLegacyLink } from "../../../../helpers/LegacyLinkReplaceHelper.js"
|
||||
export default {
|
||||
name: "GeneralComponent",
|
||||
name: "NewsContentType",
|
||||
props:{
|
||||
content:{
|
||||
type:String,
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
required:true,
|
||||
},
|
||||
content_id:{
|
||||
type:Number,
|
||||
type: [Number, String],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -129,7 +129,7 @@ export default {
|
||||
template: /*html*/ `
|
||||
<!-- div that contains the content -->
|
||||
<!-- TODO: test with more img content from cms-->
|
||||
<div v-if="imgContent"><img v-bind="imgContent"></img></div>
|
||||
<div v-if="imgContent"><img v-bind="imgContent"/></div>
|
||||
<div v-html="content" v-else-if="content" ></div>
|
||||
<p v-else>Content was not found</p>
|
||||
`,
|
||||
|
||||
@@ -43,6 +43,13 @@ export default {
|
||||
return menuItem.c4_link ?? null;
|
||||
}
|
||||
},
|
||||
getMenuName(menuItem) {
|
||||
if(menuItem.phrase) {
|
||||
return this.$p.t(menuItem.phrase)
|
||||
} else {
|
||||
return menuItem.name
|
||||
}
|
||||
}
|
||||
},
|
||||
template:/*html*/`
|
||||
<div v-if="!menu">{{$p.t('lehre','lehrveranstaltungsUnavailable')}}</div>
|
||||
@@ -54,7 +61,7 @@ export default {
|
||||
:disabled="c4_disabled(menuItem)" :data-bs-toggle="menuItem.c4_moodle_links?.length?'dropdown':null"
|
||||
class="menu-entry p-2 w-100 text-wrap border border-1 rounded-3 d-flex flex-column align-items-center justify-content-center text-center text-decoration-none link h-100">
|
||||
<img :src="menuItem.c4_icon" :alt="menuItem.name" />
|
||||
<p class="w-100 mt-2 mb-0">{{menuItem.name}}</p>
|
||||
<p class="w-100 mt-2 mb-0">{{ getMenuName(menuItem) }}</p>
|
||||
<a v-for="([text,link],index) in menuItem.c4_linkList" target="_blank" :href="link" class="my-1 w-100 submenu text-decoration-none" :index="index">{{text}}</a>
|
||||
</a>
|
||||
<ul v-if="menuItem.c4_moodle_links?.length" class="dropdown-menu p-0" :aria-labelledby="menuItem.name">
|
||||
|
||||
@@ -19,6 +19,7 @@ export default {
|
||||
props: {
|
||||
lehrveranstaltung_id: Number,
|
||||
bezeichnung: String,
|
||||
bezeichnung_eng: String,
|
||||
module: String,
|
||||
farbe: String,
|
||||
lvinfo: Boolean,
|
||||
|
||||
@@ -75,21 +75,26 @@ export default {
|
||||
this.$refs.studiensemester.selectedIndex++;
|
||||
this.$refs.studiensemester.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
},
|
||||
setHash(val) {
|
||||
// TODO: make this a router param to enable history
|
||||
location.hash = val;
|
||||
updateRouter(val) {
|
||||
this.$router.push(`/Cis/MyLv/${val}`);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Cis/Mylv/Studiensemester').then(res => {
|
||||
this.studiensemester = res.data.retval || [];
|
||||
const hash = location.hash.substring(1);
|
||||
if (hash && this.studiensemester.filter(s => s.studiensemester_kurzbz == hash).length)
|
||||
this.currentSemester = hash;
|
||||
const routerStudiensemester = this.$route.params.studiensemester;
|
||||
if (routerStudiensemester && this.studiensemester.filter(s => s.studiensemester_kurzbz == routerStudiensemester).length)
|
||||
this.currentSemester = routerStudiensemester;
|
||||
else
|
||||
this.currentSemester = this.nearestSem;
|
||||
});
|
||||
},
|
||||
beforeRouteUpdate(to, from, next){
|
||||
if (to.params.studiensemester && this.studiensemester.filter(s => s.studiensemester_kurzbz == to.params.studiensemester).length && to.params.studiensemester != this.currentSemester)
|
||||
this.currentSemester = to.params.studiensemester;
|
||||
next();
|
||||
|
||||
},
|
||||
template: `
|
||||
|
||||
<h2>{{$p.t('lehre/myLV')}}</h2>
|
||||
@@ -104,7 +109,7 @@ export default {
|
||||
<button :aria-label="$p.t('lehre','previousStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','previousStudSemester')}" class="btn btn-outline-secondary" type="button" :disabled="currentIsFirst" @click="prevSem">
|
||||
<i class="fa fa-caret-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
<select ref="studiensemester" v-model="currentSemester" class="form-select" :aria-label="$p.t('global/studiensemester_auswaehlen')" @change="setHash($event.target.value)">
|
||||
<select ref="studiensemester" v-model="currentSemester" class="form-select" :aria-label="$p.t('global/studiensemester_auswaehlen')" @change="updateRouter($event.target.value)">
|
||||
<option v-for="semester in studiensemester" :key="semester.studiensemester_kurzbz">{{semester.studiensemester_kurzbz}}</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" :aria-label="$p.t('lehre','nextStudSemester')" v-tooltip.top="{showDelay:1000, value:$p.t('lehre','nextStudSemester')}" type="button" :disabled="currentIsLast" @click="nextSem">
|
||||
|
||||
@@ -11,6 +11,7 @@ import RoleInformation from "./ProfilComponents/RoleInformation.js";
|
||||
import ProfilInformation from "./ProfilComponents/ProfilInformation.js";
|
||||
|
||||
import ApiProfilUpdate from '../../../api/factory/profilUpdate.js';
|
||||
import { dateFilter } from '../../../tabulator/filters/Dates.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -40,7 +41,7 @@ export default {
|
||||
persistence: {
|
||||
columns: false
|
||||
},
|
||||
height: 300,
|
||||
minHeight: 300,
|
||||
layout: "fitColumns",
|
||||
responsiveLayout: "collapse",
|
||||
responsiveLayoutCollapseUseFormatters: false,
|
||||
@@ -74,18 +75,24 @@ export default {
|
||||
{
|
||||
title: Vue.computed(() => this.preloadedPhrasen.gueltigVonPhrase),
|
||||
field: "Gültig_von",
|
||||
headerFilter: true,
|
||||
headerFilterFunc: 'dates',
|
||||
headerFilter: dateFilter,
|
||||
resizable: true,
|
||||
minWidth: 200,
|
||||
visible: true
|
||||
visible: true,
|
||||
formatter:"datetime",
|
||||
formatterParams: this.datetimeFormatterParams()
|
||||
},
|
||||
{
|
||||
title: Vue.computed(() => this.preloadedPhrasen.gueltigBisPhrase),
|
||||
field: "Gültig_bis",
|
||||
headerFilter: true,
|
||||
headerFilterFunc: 'dates',
|
||||
headerFilter: dateFilter,
|
||||
resizable: true,
|
||||
minWidth: 200,
|
||||
visible: true
|
||||
visible: true,
|
||||
formatter:"datetime",
|
||||
formatterParams: this.datetimeFormatterParams()
|
||||
},
|
||||
{
|
||||
title: Vue.computed(() => this.preloadedPhrasen.wochenstundenPhrase),
|
||||
@@ -102,7 +109,7 @@ export default {
|
||||
persistence: {
|
||||
columns: false
|
||||
},
|
||||
height: 300,
|
||||
minHeight: 300,
|
||||
layout: "fitColumns",
|
||||
responsiveLayout: "collapse",
|
||||
responsiveLayoutCollapseUseFormatters: false,
|
||||
@@ -138,9 +145,12 @@ export default {
|
||||
{
|
||||
title: Vue.computed(() => this.preloadedPhrasen.ausgabedatumPhrase),
|
||||
field: "Ausgegeben_am",
|
||||
headerFilter: true,
|
||||
headerFilterFunc: 'dates',
|
||||
headerFilter: dateFilter,
|
||||
minWidth: 200,
|
||||
visible: true
|
||||
visible: true,
|
||||
formatter:"datetime",
|
||||
formatterParams: this.datetimeFormatterParams()
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -167,11 +177,11 @@ export default {
|
||||
this.$api
|
||||
.call(ApiProfilUpdate.selectProfilRequest())
|
||||
.then((request) => {
|
||||
if (!request.error && request) {
|
||||
if (!request.error && request.data) {
|
||||
this.data.profilUpdates = request.data;
|
||||
this.data.profilUpdates.sort(this.sortProfilUpdates);
|
||||
} else {
|
||||
console.error("Error when fetching profile updates: " + res.data);
|
||||
console.error("Error when fetching profile updates: " + request);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -211,6 +221,15 @@ export default {
|
||||
setTableColumnTitles() { // reevaluates computed phrasen
|
||||
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
|
||||
if(this.$refs.funktionenTable) this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns)
|
||||
},
|
||||
datetimeFormatterParams: function() {
|
||||
const params = {
|
||||
inputFormat:"yyyy-MM-dd",
|
||||
outputFormat:"dd.MM.yyyy",
|
||||
invalidPlaceholder:"(invalid date)",
|
||||
timezone:FHC_JS_DATA_STORAGE_OBJECT.timezone
|
||||
};
|
||||
return params;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import RoleInformation from "./ProfilComponents/RoleInformation.js";
|
||||
import ProfilEmails from "./ProfilComponents/ProfilEmails.js";
|
||||
import ProfilInformation from "./ProfilComponents/ProfilInformation.js";
|
||||
|
||||
import { dateFilter } from '../../../tabulator/filters/Dates.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoreFilterCmpt,
|
||||
@@ -24,7 +26,7 @@ export default {
|
||||
persistence: {
|
||||
columns: false
|
||||
},
|
||||
height: 300,
|
||||
minHeight: 300,
|
||||
layout: "fitColumns",
|
||||
responsiveLayout: "collapse",
|
||||
responsiveLayoutCollapseUseFormatters: false,
|
||||
@@ -60,18 +62,24 @@ export default {
|
||||
{
|
||||
title: Vue.computed(() => this.$p.t('global/gueltigVon')),
|
||||
field: "Gültig_von",
|
||||
headerFilter: true,
|
||||
headerFilterFunc: 'dates',
|
||||
headerFilter: dateFilter,
|
||||
resizable: true,
|
||||
minWidth: 200,
|
||||
visible: true
|
||||
visible: true,
|
||||
formatter:"datetime",
|
||||
formatterParams: this.datetimeFormatterParams()
|
||||
},
|
||||
{
|
||||
title: Vue.computed(() => this.$p.t('global/gueltigBis')),
|
||||
field: "Gültig_bis",
|
||||
headerFilter: true,
|
||||
headerFilterFunc: 'dates',
|
||||
headerFilter: dateFilter,
|
||||
resizable: true,
|
||||
minWidth: 200,
|
||||
visible: true
|
||||
visible: true,
|
||||
formatter:"datetime",
|
||||
formatterParams: this.datetimeFormatterParams()
|
||||
},
|
||||
{
|
||||
title: Vue.computed(() => this.$p.t('profil/wochenstunden')),
|
||||
@@ -91,6 +99,15 @@ export default {
|
||||
funktionenTableBuilt: function () {
|
||||
this.$refs.funktionenTable.tabulator.setData(this.data.funktionen);
|
||||
},
|
||||
datetimeFormatterParams: function() {
|
||||
const params = {
|
||||
inputFormat:"yyyy-MM-dd",
|
||||
outputFormat:"dd.MM.yyyy",
|
||||
invalidPlaceholder:"(invalid date)",
|
||||
timezone:FHC_JS_DATA_STORAGE_OBJECT.timezone
|
||||
};
|
||||
return params;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'data.funktionen'(newVal) {
|
||||
|
||||
@@ -88,6 +88,18 @@ export default {
|
||||
if (isMitarbeiter) {
|
||||
content["isMitarbeiter"] = isMitarbeiter;
|
||||
}
|
||||
|
||||
const filesFromDatabase =
|
||||
await this.$api
|
||||
.call(ApiProfilUpdate.getProfilRequestFiles(
|
||||
updateRequest.profil_update_id
|
||||
))
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
});
|
||||
|
||||
files = filesFromDatabase;
|
||||
content["files"] = files;
|
||||
}
|
||||
|
||||
//? adds the status information if the profil update request was rejected or accepted
|
||||
|
||||
@@ -10,21 +10,34 @@ export default {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'studiengang_kz', // inject info that should not be displayed
|
||||
],
|
||||
inject: {
|
||||
// inject info that should not be displayed
|
||||
'studiengang_kz': {
|
||||
from: 'studiengang_kz',
|
||||
default: false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
getLinkGruppeListe() {
|
||||
if(this.studiengang_kz === false) {
|
||||
return '';
|
||||
}
|
||||
return this.data.gruppe?.value && this.data.verband?.value && this.data.semester?.value ? FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'cis/private/stud_in_grp.php?kz='+this.studiengang_kz+'&sem=' + this.data.semester.value
|
||||
+ '&verband=' + this.data.verband.value + '&grp=' + this.data.gruppe.value : ''
|
||||
},
|
||||
getLinkVerbandListe() {
|
||||
if(this.studiengang_kz === false) {
|
||||
return '';
|
||||
}
|
||||
return this.data.verband?.value && this.data.semester?.value ? FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'cis/private/stud_in_grp.php?kz='+this.studiengang_kz+'&sem=' + this.data.semester.value
|
||||
+ '&verband=' + this.data.verband.value : ''
|
||||
},
|
||||
getLinkSemesterListe() {
|
||||
if(this.studiengang_kz === false) {
|
||||
return '';
|
||||
}
|
||||
return this.data.semester?.value ? FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ 'cis/private/stud_in_grp.php?kz='+this.studiengang_kz+'&sem=' + this.data.semester.value : ''
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import Dms from "../../../../Form/Upload/Dms.js";
|
||||
|
||||
import ApiProfil from '../../../../../api/factory/profil.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AutoComplete: primevue.autocomplete,
|
||||
Dms: Dms
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -11,9 +14,13 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
files: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
},
|
||||
|
||||
inject: ["getZustelladressenCount"],
|
||||
inject: ["getZustelladressenCount", "updateFileID"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
@@ -23,6 +30,8 @@ export default {
|
||||
nationenList: [],
|
||||
originalValue: null,
|
||||
zustellAdressenCount: null,
|
||||
dmsData: [],
|
||||
fileschanged: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -83,6 +92,11 @@ export default {
|
||||
//? sets the value of a property to null when an empty string is entered to keep the isChanged function valid
|
||||
if (bind === "zustelladresse") {
|
||||
this.data[bind] = event.target.checked;
|
||||
} else if(bind === 'files') {
|
||||
if(this.dmsData.length > 0 && this.dmsData[0].type !== 'application/x.fhc-dms+json') {
|
||||
this.fileschanged = true;
|
||||
}
|
||||
this.updateFileID(this.dmsData);
|
||||
} else {
|
||||
this.data[bind] = event.target.value === "" ? null : event.target.value;
|
||||
}
|
||||
@@ -91,6 +105,11 @@ export default {
|
||||
// update the zustellAdressen count
|
||||
this.zustellAdressenCount = this.getZustelladressenCount();
|
||||
},
|
||||
|
||||
deleteDmsData: function() {
|
||||
this.dmsData = [];
|
||||
this.updateValue(null, 'files');
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -111,12 +130,14 @@ export default {
|
||||
!this.data.strasse ||
|
||||
!this.data.plz ||
|
||||
!this.data.ort ||
|
||||
!this.data.typ
|
||||
!this.data.typ ||
|
||||
this.dmsData.length === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.originalValue !== JSON.stringify(this.data);
|
||||
const datachanged = this.originalValue !== JSON.stringify(this.data);
|
||||
return datachanged || this.fileschanged;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -133,6 +154,12 @@ export default {
|
||||
this.zustellAdressenCount = this.getZustelladressenCount();
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.files) {
|
||||
this.dmsData = this.files;
|
||||
}
|
||||
},
|
||||
|
||||
template: /*html*/ `
|
||||
<div class="gy-3 row justify-content-center align-items-center">
|
||||
<!-- warning message for too many zustellungs Adressen -->
|
||||
@@ -212,9 +239,28 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col">
|
||||
<div class="form-underline-titel">{{$p.t('profilUpdate','meldebestaetigung')}}*</div>
|
||||
<dms
|
||||
ref="update"
|
||||
id="files"
|
||||
name="files"
|
||||
:multiple="false"
|
||||
v-model="dmsData"
|
||||
@update:model-value="updateValue($event,'files')"
|
||||
></dms>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div> </div>
|
||||
<button
|
||||
@click="deleteDmsData"
|
||||
class="btn btn-danger"
|
||||
:aria-label="$p.t('profilUpdate','deleteAttachment')"
|
||||
:title="$p.t('profilUpdate','deleteAttachment')"
|
||||
><i style="color:white" class="fa fa-trash" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`,
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
mixins: [BsModal],
|
||||
props: {
|
||||
titel: {
|
||||
type: Object,
|
||||
type: String,
|
||||
},
|
||||
files: {
|
||||
type: Array,
|
||||
@@ -57,7 +57,7 @@ export default {
|
||||
},
|
||||
template: /*html*/`
|
||||
|
||||
<bs-modal v-show="!loading" ref="modalContainer" v-bind="$props" body-class="" dialog-class="modal-lg" class="bootstrap-alert" :backdrop="false">
|
||||
<bs-modal ref="modalContainer" v-bind="$props" body-class="" dialog-class="modal-lg" class="bootstrap-alert" :backdrop="false">
|
||||
<template #title>
|
||||
<p style="opacity:0.8" class="ms-2" v-if="!updateID">{{$p.t('profilUpdate','profilBildUpdateMessage',[titel])}}</p>
|
||||
</template>
|
||||
|
||||
@@ -112,14 +112,17 @@ export default {
|
||||
<div class="form-underline-titel">{{topic}}</div>
|
||||
<span class="form-underline-content">{{data.value}} </span>
|
||||
</div>
|
||||
<div v-if="files?.length" class="ms-2">
|
||||
|
||||
<a target="_blank" :href="getDocumentLink(file.dms_id)" v-for="file in files">{{file.name}}</a>
|
||||
</div>
|
||||
</template>
|
||||
<component v-else :is="getComponentView" :data="data"></component>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="files?.length" class="card mt-4">
|
||||
<div class="card-header">{{$p.t('profilUpdate','nachweisdokumente')}}</div>
|
||||
<div class="card-body">
|
||||
<a target="_blank" :href="getDocumentLink(file.dms_id)" v-for="file in files">{{file.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import FetchProfilUpdates from "./ProfilComponents/FetchProfilUpdates.js";
|
||||
import EditProfil from "./ProfilModal/EditProfil.js";
|
||||
|
||||
import ApiProfilUpdate from '../../../api/factory/profilUpdate.js';
|
||||
import { dateFilter } from '../../../tabulator/filters/Dates.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -39,7 +40,7 @@ export default {
|
||||
persistence: {
|
||||
columns: false
|
||||
},
|
||||
height: 200,
|
||||
minHeight: 200,
|
||||
layout: "fitColumns",
|
||||
columns: [{
|
||||
title: Vue.computed(() => this.preloadedPhrasen.zutrittsGruppenPhrase),
|
||||
@@ -51,7 +52,7 @@ export default {
|
||||
persistence: {
|
||||
columns: false
|
||||
},
|
||||
height: 300,
|
||||
minHeight: 300,
|
||||
layout: "fitColumns",
|
||||
responsiveLayout: "collapse",
|
||||
responsiveLayoutCollapseUseFormatters: false,
|
||||
@@ -85,9 +86,12 @@ export default {
|
||||
{
|
||||
title: Vue.computed(() =>this.preloadedPhrasen.ausgabedatum) ,
|
||||
field: "Ausgegeben_am",
|
||||
headerFilter: true,
|
||||
headerFilterFunc: 'dates',
|
||||
headerFilter: dateFilter,
|
||||
minWidth: 200,
|
||||
visible: true
|
||||
visible: true,
|
||||
formatter:"datetime",
|
||||
formatterParams: this.datetimeFormatterParams()
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -133,11 +137,11 @@ export default {
|
||||
this.$api
|
||||
.call(ApiProfilUpdate.selectProfilRequest())
|
||||
.then((request) => {
|
||||
if (!request.error && res) {
|
||||
if (!request.error && request.data) {
|
||||
this.data.profilUpdates = request.data;
|
||||
this.data.profilUpdates.sort(this.sortProfilUpdates);
|
||||
} else {
|
||||
console.error("Error when fetching profile updates: " + res.data);
|
||||
console.error("Error when fetching profile updates: " + request);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -160,6 +164,15 @@ export default {
|
||||
this.$refs.editModal.show();
|
||||
});
|
||||
},
|
||||
datetimeFormatterParams: function() {
|
||||
const params = {
|
||||
inputFormat:"yyyy-MM-dd",
|
||||
outputFormat:"dd.MM.yyyy",
|
||||
invalidPlaceholder:"(invalid date)",
|
||||
timezone:FHC_JS_DATA_STORAGE_OBJECT.timezone
|
||||
};
|
||||
return params;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
@@ -188,16 +188,21 @@ export default {
|
||||
|
||||
<span class="form-underline-content" >{{data.requested_change.value}}</span>
|
||||
</div>
|
||||
<div v-if="files?.length" class="ms-2">
|
||||
|
||||
<a v-for="file in files" target="_blank" :href="getDocumentLink(file.dms_id)" >{{file.name}}</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<component v-else :is="getComponentView" :withZustelladresse="getComponentView==='adresse'?true:false" :data="data.requested_change"></component>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="files?.length" class="card mt-3">
|
||||
<div class="card-header">{{$p.t('profilUpdate','nachweisdokumente')}}</div>
|
||||
<div class="card-body">
|
||||
<a v-for="file in files" target="_blank" :href="getDocumentLink(file.dms_id)" >{{file.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ export default {
|
||||
Loading,
|
||||
AcceptDenyUpdate,
|
||||
},
|
||||
inject: ["profilUpdateTopic", "profilUpdateStates"],
|
||||
inject: ["profilUpdateStates"],
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -44,51 +44,53 @@ export default {
|
||||
modalData: null,
|
||||
loading: false,
|
||||
filter: "Pending",
|
||||
events: [],
|
||||
profil_update_id: Number(this.id),
|
||||
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
profilUpdateOptions: function(){
|
||||
return {
|
||||
ajaxURL:
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
`/Cis/ProfilUpdate/`,
|
||||
|
||||
ajaxURLGenerator: (url, config, params) => {
|
||||
//? this function needs to be an array function in order to access the this properties of the Vue component
|
||||
|
||||
switch (this.filter) {
|
||||
case this.profilUpdateStates["Pending"]:
|
||||
return (
|
||||
url +
|
||||
`getProfilUpdateWithPermission/${this.profilUpdateStates["Pending"]}`
|
||||
);
|
||||
case this.profilUpdateStates["Accepted"]:
|
||||
return (
|
||||
url +
|
||||
`getProfilUpdateWithPermission/${this.profilUpdateStates["Accepted"]}`
|
||||
);
|
||||
case this.profilUpdateStates["Rejected"]:
|
||||
return (
|
||||
url +
|
||||
`getProfilUpdateWithPermission/${this.profilUpdateStates["Rejected"]}`
|
||||
);
|
||||
default:
|
||||
return url + `getProfilUpdateWithPermission`;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
profilUpdateEvents: function () {
|
||||
return [
|
||||
{
|
||||
"event": "dataProcessed",
|
||||
"handler": this.handleDataProcessed
|
||||
}
|
||||
];
|
||||
},
|
||||
profilUpdateOptions: function () {
|
||||
return {
|
||||
ajaxURL: 'dummy',
|
||||
ajaxRequestFunc: (url, config, params) => {
|
||||
return this.$api.call(ApiProfilUpdate.getProfilUpdateWithPermission(params.filter));
|
||||
},
|
||||
ajaxParams: () => {
|
||||
let filter = '';
|
||||
switch (this.filter) {
|
||||
case this.profilUpdateStates["Pending"]:
|
||||
filter = this.profilUpdateStates["Pending"];
|
||||
break;
|
||||
case this.profilUpdateStates["Accepted"]:
|
||||
filter = this.profilUpdateStates["Accepted"];
|
||||
break;
|
||||
case this.profilUpdateStates["Rejected"]:
|
||||
filter = this.profilUpdateStates["Rejected"];
|
||||
break;
|
||||
default:
|
||||
filter = '';
|
||||
}
|
||||
return {
|
||||
"filter": filter
|
||||
};
|
||||
},
|
||||
ajaxResponse: (url, params, response) => {
|
||||
//url - the URL of the request
|
||||
//params - the parameters passed with the request
|
||||
//response - the JSON object returned in the body of the response.
|
||||
//? sorts the response data from the backend
|
||||
if (response)
|
||||
response.sort((ele1, ele2) => sortProfilUpdates(ele1, ele2, this));
|
||||
if (response?.data)
|
||||
response.data.sort((ele1, ele2) => sortProfilUpdates(ele1, ele2, this));
|
||||
|
||||
return response;
|
||||
return response.data;
|
||||
},
|
||||
//? adds tooltip with the status message of a profil update request if its status is not pending
|
||||
columnDefaults: {
|
||||
@@ -356,7 +358,19 @@ export default {
|
||||
this.$refs.UpdatesTable.tabulator.setData();
|
||||
//? store the selected view in the session storage of the browser
|
||||
sessionStorage.setItem("filter", event.target.value);
|
||||
},
|
||||
},
|
||||
handleDataProcessed: function () {
|
||||
if (this.profil_update_id) {
|
||||
const arrayRowData = this.$refs.UpdatesTable.tabulator
|
||||
.getData()
|
||||
.filter((row) => {
|
||||
return row.profil_update_id === this.profil_update_id;
|
||||
});
|
||||
if (arrayRowData.length) {
|
||||
this.showAcceptDenyModal(arrayRowData[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
loading: function (newValue, oldValue) {
|
||||
@@ -374,20 +388,7 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
//? opens the AcceptDenyUpdate Modal if a preselected profil_update_id was passed to the component (used for email links)
|
||||
if (this.profil_update_id) {
|
||||
this.$refs.UpdatesTable.tabulator.on("dataProcessed", () => {
|
||||
const arrayRowData = this.$refs.UpdatesTable.tabulator
|
||||
.getData()
|
||||
.filter((row) => {
|
||||
return row.profil_update_id === this.profil_update_id;
|
||||
});
|
||||
if (arrayRowData.length) {
|
||||
this.showAcceptDenyModal(arrayRowData[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//? opens the AcceptDenyUpdate Modal if a preselected profil_update_id was passed to the component (used for email links)
|
||||
if (sessionStorage.getItem("filter")) {
|
||||
this.filter = sessionStorage.getItem("filter");
|
||||
}
|
||||
@@ -409,7 +410,7 @@ export default {
|
||||
</div>
|
||||
<loading ref="loadingModalRef" :timeout="0"></loading>
|
||||
|
||||
<core-filter-cmpt v-if="profilUpdateStates && categoryLoaded" :title="$p.t('profilUpdate','profilUpdateRequests')" ref="UpdatesTable" :tabulatorEvents="events" :tabulator-options="profilUpdateOptions" tableOnly :sideMenu="false" />
|
||||
<core-filter-cmpt v-if="profilUpdateStates && categoryLoaded" :title="$p.t('profilUpdate','profilUpdateRequests')" ref="UpdatesTable" :tabulatorEvents="profilUpdateEvents" :tabulator-options="profilUpdateOptions" tableOnly :sideMenu="false" />
|
||||
|
||||
</div>`,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
|
||||
import ApiOrt from '../../../api/factory/ort.js'
|
||||
export const Raumsuche = {
|
||||
name: "Raumsuche",
|
||||
props: {
|
||||
@@ -13,6 +13,8 @@ export const Raumsuche = {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
phrasenPromise: null,
|
||||
phrasenResolved: false,
|
||||
tabulatorUuid: Vue.ref(0),
|
||||
tableBuiltResolve: null,
|
||||
tableBuiltPromise: null,
|
||||
@@ -54,8 +56,8 @@ export const Raumsuche = {
|
||||
columns: [
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/raum_kurzbz')), field: 'ort_kurzbz', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('global/bezeichnung')), field: 'bezeichnung', widthGrow: 2},
|
||||
{title: Vue.computed(() => this.$p.t('global/nummer')), field: 'nummer', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('global/personen')), field: 'personen', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/raumnummer')), field: 'nummer', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/personcap')), field: 'personen', widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/rauminfo')),
|
||||
field: 'linkInfo', formatter: this.linkFormatter, widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$p.t('rauminfo/roomReservations')),
|
||||
@@ -68,17 +70,6 @@ export const Raumsuche = {
|
||||
handler: async () => {
|
||||
this.tableBuiltResolve()
|
||||
}
|
||||
},
|
||||
{
|
||||
event: "cellClick",
|
||||
handler: async (e, cell) => {
|
||||
|
||||
if((cell.column.field === 'linkInfo' || cell.column.field === 'linkRes') && cell.value){
|
||||
window.open(cell.value, '_blank');
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
]};
|
||||
},
|
||||
@@ -132,7 +123,8 @@ export const Raumsuche = {
|
||||
this.$refs.raumsucheTable.tabulator.setData(d);
|
||||
},
|
||||
loadRoomTypes() {
|
||||
this.$fhcApi.factory.ort.getRoomTypes().then(res => {
|
||||
this.$api.call(ApiOrt.getRoomTypes())
|
||||
.then(res => {
|
||||
res?.data?.forEach(type => {
|
||||
type.beschreibung = type.beschreibung.replace('&', '&')
|
||||
})
|
||||
@@ -141,7 +133,7 @@ export const Raumsuche = {
|
||||
})
|
||||
},
|
||||
loadRooms() {
|
||||
this.$fhcApi.factory.ort.getRooms(this.datum.toISOString(), this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl)
|
||||
this.$api.call(ApiOrt.getRooms(this.datum.toISOString(), this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl))
|
||||
.then(res => {
|
||||
if(res?.data?.retval) this.setupData(res.data.retval)
|
||||
})
|
||||
@@ -167,6 +159,7 @@ export const Raumsuche = {
|
||||
return `${hours}:${minutes}`;
|
||||
},
|
||||
async setupMounted() {
|
||||
|
||||
this.tableBuiltPromise = new Promise(this.tableResolve)
|
||||
await this.tableBuiltPromise
|
||||
|
||||
@@ -182,7 +175,7 @@ export const Raumsuche = {
|
||||
if(this.$refs.raumsucheTable) {
|
||||
this.$refs.raumsucheTable.$refs.table.style.setProperty('height', h+'px')
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -191,7 +184,8 @@ export const Raumsuche = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
this.phrasenPromise = this.$p.loadCategory(['rauminfo', 'global'])
|
||||
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
|
||||
},
|
||||
mounted() {
|
||||
this.setupMounted()
|
||||
@@ -239,7 +233,7 @@ export const Raumsuche = {
|
||||
</VueDatePicker>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-auto">
|
||||
<div class="col-12 col-lg-3">
|
||||
<select ref="raumtyp" id="raumtypSelect" v-model="selectedType" class="form-select"
|
||||
:aria-label="$p.t('global/studiensemester_auswaehlen')" @change="setRoute($event.target.value)">
|
||||
<option :key="defaultType" selected :value="defaultType">{{defaultType.beschreibung}}</option>
|
||||
@@ -248,16 +242,21 @@ export const Raumsuche = {
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-4 col-lg-2">
|
||||
<InputNumber v-model="anzahl" :prefix="$p.t('rauminfo/anzahlPersonen') + ': '" inputId="anzahlInput" :min="1" :max="100" />
|
||||
<div class="col-12 col-lg-3">
|
||||
<InputNumber v-model="anzahl"
|
||||
:prefix="$p.t('rauminfo/minCapacity') + ': '"
|
||||
inputId="anzahlInput" :min="1" :max="1000"
|
||||
:style="{'width': '100%'}"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-8 col-lg-2 d-flex justify-content-center align-items-center">
|
||||
<div class="col-12 col-lg-2">
|
||||
<button class="btn btn-primary border-0" @click="search">{{ $p.t('rauminfo/roomSearch') }} <i class="fa fa-magnifying-glass"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<core-filter-cmpt
|
||||
v-if="phrasenResolved"
|
||||
@uuidDefined="handleUuidDefined"
|
||||
:title="''"
|
||||
ref="raumsucheTable"
|
||||
|
||||
@@ -23,14 +23,6 @@ export default {
|
||||
this.$p.t('person/ort'),
|
||||
this.event.ort_kurzbz
|
||||
].join(": "));
|
||||
|
||||
this.event.lektor = [
|
||||
this.event.lektor[0],
|
||||
this.event.lektor[0],
|
||||
this.event.lektor[0],
|
||||
this.event.lektor[0],
|
||||
this.event.lektor[0],
|
||||
];
|
||||
|
||||
if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) {
|
||||
if (this.event.lektor.length > 3) {
|
||||
|
||||
@@ -156,7 +156,7 @@ export default {
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!hidden || editMode" :id="widgetID" class="dashboard-item card overflow-hidden h-100 position-relative" :class="{'draggedItem':dragstate, 'dashboard-item-overlay':resizeOverlay, [arguments?.className]:arguments && arguments.className}">
|
||||
<div v-else-if="!hidden || editMode" :id="widgetID" class="dashboard-item card overflow-hidden h-100 position-relative" :class="{'hiddenWidget':hidden, 'draggedItem':dragstate, 'dashboard-item-overlay':resizeOverlay, [arguments?.className]:arguments && arguments.className}">
|
||||
<div v-show="!dragstate" class="h-100 card border-0">
|
||||
<div v-if="widget" class="card-header d-flex ps-0 pe-2 align-items-center">
|
||||
<Transition>
|
||||
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
setPrev() {
|
||||
const thisIndex = this.allNewsList.findIndex(n=>n.news_id == this.selected.news_id)
|
||||
const prevIndex = thisIndex ? thisIndex - 1 : this.allNewsList.length - 1
|
||||
|
||||
|
||||
this.setSelected(this.allNewsList[prevIndex])
|
||||
this.updateNewsContentClasses();
|
||||
},
|
||||
@@ -118,14 +118,14 @@ export default {
|
||||
return classString
|
||||
},
|
||||
async setSelected(news) {
|
||||
let clickedElement = document.getElementById('card-'+news.news_id);
|
||||
let clickedElementIndex = this.allNewsList.indexOf(news);
|
||||
let oldElementIndex = this.allNewsList.indexOf(this.selected);
|
||||
const clickedElement = document.getElementById('card-'+news.news_id);
|
||||
const clickedElementIndex = this.allNewsList.indexOf(news);
|
||||
const oldElementIndex = this.allNewsList.indexOf(this.selected);
|
||||
|
||||
//if the clicked element is already active, do nothing
|
||||
if(clickedElementIndex === oldElementIndex) return;
|
||||
//add prev/next class to the clicked element
|
||||
if(clickedElementIndex > oldElementIndex){
|
||||
if(clickedElementIndex > oldElementIndex) {
|
||||
clickedElement.classList.add('carousel-item-next');
|
||||
}else{
|
||||
clickedElement.classList.add('carousel-item-prev');
|
||||
|
||||
@@ -159,7 +159,7 @@ export default {
|
||||
// this.$emit('setConfig', true); -> use this to enable widget config mode if needed
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="widgets-url w-100 h-100 overflow-scroll" style="padding: 1rem 1rem;">
|
||||
<div class="widgets-url w-100 h-100 overflow-auto" style="padding: 1rem 1rem;">
|
||||
<div class="d-flex flex-column justify-content-between">
|
||||
<button class="btn btn-outline-secondary btn-sm w-100 mt-2 card" @click="openCreateModal" type="button">{{$p.t('bookmark','newLink')}}</button>
|
||||
|
||||
|
||||
@@ -274,8 +274,23 @@ export default {
|
||||
dragging(event){
|
||||
if(this.mode == MODE_MOVE){
|
||||
this.toggleDraggedItemOverlay(true);
|
||||
this.clonedWidget.style.top = `${this.clientY-20}px`;
|
||||
this.clonedWidget.style.left = `${this.clientX-15}px`;
|
||||
|
||||
const containerRect = this.$refs.container.getBoundingClientRect();
|
||||
const clonedWidgetRect = this.clonedWidget.getBoundingClientRect();
|
||||
|
||||
let desiredTop = this.clientY - 20;
|
||||
let desiredLeft = this.clientX - 15;
|
||||
|
||||
const minTop = 0;
|
||||
const maxTop = containerRect.height - clonedWidgetRect.height;
|
||||
const minLeft = 0;
|
||||
const maxLeft = containerRect.width - clonedWidgetRect.width;
|
||||
|
||||
const constrainedTop = Math.max(minTop, Math.min(maxTop, desiredTop));
|
||||
const constrainedLeft = Math.max(minLeft, Math.min(maxLeft, desiredLeft));
|
||||
|
||||
this.clonedWidget.style.top = `${constrainedTop}px`;
|
||||
this.clonedWidget.style.left = `${constrainedLeft}px`;
|
||||
}
|
||||
},
|
||||
createNewGrid(items) {
|
||||
@@ -413,11 +428,14 @@ export default {
|
||||
setTimeout(() => {
|
||||
this.draggedNode = evt.target.closest(".drop-grid-item");
|
||||
//clones the widget for the drag Image
|
||||
|
||||
let clone = evt.target.closest(".drop-grid-item")?.cloneNode(true);
|
||||
|
||||
clone.style.zIndex = 5;
|
||||
clone.classList.add("widgetClone");
|
||||
this.$refs.container.appendChild(clone);
|
||||
const hiddenWidget = clone.querySelector("[style='display: none;']");
|
||||
hiddenWidget.style.removeProperty("display");
|
||||
this.clonedWidget = clone;
|
||||
}, 0);
|
||||
|
||||
@@ -434,12 +452,9 @@ export default {
|
||||
},
|
||||
dragOver(evt) {
|
||||
if ((this.y + 1) > this.rows && (this.mode == MODE_MOVE || this.mode == MODE_RESIZE)) {
|
||||
this.positionUpdates = this.positionUpdates?.filter(item => {
|
||||
return item.widgetid == this.draggedItem.data.widgetid;
|
||||
})
|
||||
this.dragEnd();
|
||||
this.dragCancel();
|
||||
}
|
||||
|
||||
}
|
||||
if (!this.active)
|
||||
return this.dragCancel();
|
||||
this.checkPinnedWidgetAnimation();
|
||||
@@ -454,17 +469,17 @@ export default {
|
||||
let x = this.x + this.draggedOffset[0];
|
||||
let y = this.y + this.draggedOffset[1];
|
||||
if (x < 0) {
|
||||
this.draggedOffset[0] -= x;
|
||||
this.draggedOffset[0] += x;
|
||||
x = 0;
|
||||
} else if (x + this.draggedItem.w > this.cols) {
|
||||
this.draggedOffset[0] += this.cols - this.draggedItem.w - x;
|
||||
x = this.cols - this.draggedItem.w;
|
||||
}
|
||||
if (y < 0) {
|
||||
this.draggedOffset[1] -= y;
|
||||
this.draggedOffset[1] += y;
|
||||
y = 0;
|
||||
}
|
||||
this.positionUpdates = this.dragGrid.move(this.draggedItem, x, y);
|
||||
this.positionUpdates= this.dragGrid.move(this.draggedItem, x, y);
|
||||
break;
|
||||
}
|
||||
case MODE_RESIZE: {
|
||||
@@ -481,6 +496,7 @@ export default {
|
||||
}
|
||||
},
|
||||
dragCancel() {
|
||||
this.removeWidgetClones();
|
||||
this.additionalRowComputed = false;
|
||||
this.toggleDraggedItemOverlay(false);
|
||||
this.mode = MODE_IDLE;
|
||||
@@ -492,8 +508,12 @@ export default {
|
||||
|
||||
},
|
||||
dragEnd() {
|
||||
if (this.mode == MODE_IDLE)
|
||||
this.removeWidgetClones();
|
||||
this.toggleDraggedItemOverlay(false);
|
||||
|
||||
if (this.mode == MODE_IDLE){
|
||||
return;
|
||||
}
|
||||
// clean up unused classes
|
||||
let draggedItemNode = document.getElementById(this.draggedItem.data.widgetid);
|
||||
draggedItemNode.classList.remove("border-danger");
|
||||
@@ -501,19 +521,19 @@ export default {
|
||||
ele.classList.remove("denied-dragging-animation");
|
||||
})
|
||||
|
||||
let widgetClones = document.getElementsByClassName("widgetClone");
|
||||
for (let i=0; i <widgetClones.length; i++){
|
||||
this.$refs.container.removeChild(widgetClones[i]);
|
||||
}
|
||||
|
||||
if (!this.active || this.x < 0 || this.y < 0 || this.x >= this.cols)
|
||||
return this.dragCancel();
|
||||
//if (!this.active || this.x < 0 || this.y < 0 || this.x >= this.cols)
|
||||
//return this.dragCancel();
|
||||
|
||||
this.mode = MODE_IDLE;
|
||||
let updated = [];
|
||||
this.convertGridResultToUpdate(this.positionUpdates, updated);
|
||||
updated = this._updateFixedPositions(updated);
|
||||
if (updated.length)
|
||||
this.$emit('rearrangeItems', updated.filter(v => v));
|
||||
|
||||
this.draggedItem = null;
|
||||
this.draggedNode = null;
|
||||
this.$emit('draggedItem', null);
|
||||
},
|
||||
_updateFixedPositions(updated) {
|
||||
updated.forEach((item, index) => {
|
||||
@@ -597,6 +617,12 @@ export default {
|
||||
draggedItemNode.classList.remove("border-danger");
|
||||
}
|
||||
},
|
||||
removeWidgetClones(){
|
||||
let widgetClones = Array.from(document.getElementsByClassName("widgetClone"));
|
||||
for (let i = 0; i < widgetClones.length; i++) {
|
||||
this.$refs.container.removeChild(widgetClones[i]);
|
||||
}
|
||||
},
|
||||
mouseDown(){
|
||||
this.mode = MODE_MOUSE_DOWN;
|
||||
},
|
||||
@@ -612,7 +638,7 @@ export default {
|
||||
@touchmove="dragOver"
|
||||
@touchend="dragCancel"
|
||||
@dragover.prevent="dragOver"
|
||||
@drop="dragEnd"
|
||||
@drop="dragEnd($event)"
|
||||
@mousemove="updateCursorOnMouseMove"
|
||||
@mouseleave="mouseLeave">
|
||||
<TransitionGroup tag="div">
|
||||
@@ -626,7 +652,7 @@ export default {
|
||||
@mouse-up="mouseUp"
|
||||
@start-resize="startResize"
|
||||
@dragging="dragging"
|
||||
@end-drag="dragCancel"
|
||||
@end-drag="dragEnd"
|
||||
@touch-end="dragEnd();mouseUp();"
|
||||
@touch-start="updateCursorOnMouseMove($event); mouseDown();"
|
||||
class="position-absolute"
|
||||
|
||||
@@ -3,6 +3,7 @@ import FhcFragment from "../Fragment.js";
|
||||
let _uuid = {};
|
||||
|
||||
export default {
|
||||
name: "FormInput",
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
FhcFragment
|
||||
@@ -220,7 +221,7 @@ export default {
|
||||
if (this.tag == 'VueDatePicker' && !this._.components.VueDatePicker) {
|
||||
this._.components.VueDatePicker = Vue.defineAsyncComponent(() => import("../vueDatepicker.js.php"));
|
||||
} else if (this.tag == 'PvAutocomplete' && !this._.components.PvAutocomplete) {
|
||||
this._.components.PvAutocomplete = Vue.defineAsyncComponent(() => import(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/public/js/components/primevue/autocomplete/autocomplete.esm.min.js"));
|
||||
this._.components.PvAutocomplete = primevue.autocomplete;
|
||||
} else if (this.tag == 'UploadImage' && !this._.components.UploadImage) {
|
||||
this._.components.UploadImage = Vue.defineAsyncComponent(() => import("./Upload/Image.js"));
|
||||
} else if (this.tag == 'UploadDms' && !this._.components.UploadDms) {
|
||||
|
||||
@@ -23,7 +23,8 @@ export default {
|
||||
if (!Array.isArray(feedback))
|
||||
feedback = [feedback];
|
||||
const ts = Date.now();
|
||||
this.feedback[valid ? 'success' : 'danger'] = feedback.map(msg => [msg, ts]);
|
||||
this.feedback[valid ? 'success' : 'danger']
|
||||
.push(...feedback.map(msg => [msg, ts]));
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -38,33 +38,6 @@ export default {
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="row align-items-start mb-3">
|
||||
<form-input
|
||||
v-if="showLVID"
|
||||
:label="$p.t('lehre', 'lehrveranstaltung_id')"
|
||||
type="text"
|
||||
container-class="col-3"
|
||||
v-model="data.lehrveranstaltung_id"
|
||||
name="lehrveranstaltung_id"
|
||||
/>
|
||||
<form-input
|
||||
v-if="showGewichtung"
|
||||
:label="$p.t('lehre', 'gewicht')"
|
||||
type="text"
|
||||
container-class="col-3"
|
||||
v-model="data.gewicht"
|
||||
name="gewicht"
|
||||
/>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'detailanmerkung')"
|
||||
type="textarea"
|
||||
container-class="col-3"
|
||||
v-model="formattedAnmerkung"
|
||||
name="anmerkung"
|
||||
id="anmerkung"
|
||||
rows="4"
|
||||
/>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'lehrfach')"
|
||||
@@ -96,55 +69,15 @@ export default {
|
||||
{{ lehrform.bez_kurz }} {{ lehrform.bez }}
|
||||
</option>
|
||||
</form-input>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
|
||||
<form-input
|
||||
:label="$p.t('global', 'sprache')"
|
||||
type="select"
|
||||
:label="$p.t('lehre', 'detailanmerkung')"
|
||||
type="textarea"
|
||||
container-class="col-3"
|
||||
v-model="data.sprache"
|
||||
name="sprache"
|
||||
>
|
||||
<option
|
||||
v-for="sprache in dropdowns.sprachen_array"
|
||||
:key="sprache.sprache"
|
||||
:value="sprache.sprache"
|
||||
>
|
||||
{{ sprache.sprache }}
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'unr')"
|
||||
type="text"
|
||||
container-class="col-3"
|
||||
v-model="data.unr"
|
||||
name="unr"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'studiensemester')"
|
||||
type="select"
|
||||
container-class="col-3"
|
||||
v-model="data.studiensemester_kurzbz"
|
||||
name="studiensemester_kurzbz"
|
||||
>
|
||||
<option
|
||||
v-for="semester in dropdowns.studiensemester_array"
|
||||
:key="semester.studiensemester_kurzbz"
|
||||
:value="semester.studiensemester_kurzbz"
|
||||
>
|
||||
{{ semester.studiensemester_kurzbz }}
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'lehre')"
|
||||
type="checkbox"
|
||||
container-class="col-3"
|
||||
v-model="data.lehre"
|
||||
name="lehre"
|
||||
v-model="formattedAnmerkung"
|
||||
name="anmerkung"
|
||||
id="anmerkung"
|
||||
rows="10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -179,30 +112,109 @@ export default {
|
||||
{{ raumtyp.raumtyp_kurzbz }} {{ raumtyp.beschreibung }}
|
||||
</option>
|
||||
</form-input>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
:label="$p.t('global', 'sprache')"
|
||||
type="select"
|
||||
container-class="col-3"
|
||||
v-model="data.sprache"
|
||||
name="sprache"
|
||||
>
|
||||
<option
|
||||
v-for="sprache in dropdowns.sprachen_array"
|
||||
:key="sprache.sprache"
|
||||
:value="sprache.sprache"
|
||||
>
|
||||
{{ sprache.sprache }}
|
||||
</option>
|
||||
</form-input>
|
||||
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'studiensemester')"
|
||||
type="select"
|
||||
container-class="col-3"
|
||||
v-model="data.studiensemester_kurzbz"
|
||||
name="studiensemester_kurzbz"
|
||||
>
|
||||
<option
|
||||
v-for="semester in dropdowns.studiensemester_array"
|
||||
:key="semester.studiensemester_kurzbz"
|
||||
:value="semester.studiensemester_kurzbz"
|
||||
>
|
||||
{{ semester.studiensemester_kurzbz }}
|
||||
</option>
|
||||
</form-input>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'startkw')"
|
||||
type="number"
|
||||
container-class="col-2"
|
||||
min="0"
|
||||
container-class="col-1"
|
||||
v-model="data.start_kw"
|
||||
name="start_kw"
|
||||
/>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'stundenblockung')"
|
||||
type="number"
|
||||
container-class="col-2"
|
||||
min="0"
|
||||
container-class="col-1"
|
||||
v-model="data.stundenblockung"
|
||||
name="stundenblockung"
|
||||
/>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'wochenrhythmus')"
|
||||
type="number"
|
||||
container-class="col-2"
|
||||
min="0"
|
||||
container-class="col-1"
|
||||
v-model="data.wochenrythmus"
|
||||
name="wochenrythmus"
|
||||
/>
|
||||
|
||||
<div class="col-3 d-flex align-items-end">
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'lehre')"
|
||||
type="checkbox"
|
||||
v-model="data.lehre"
|
||||
name="lehre"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
v-if="showLVID"
|
||||
:label="$p.t('lehre', 'lehrveranstaltung_id')"
|
||||
type="text"
|
||||
container-class="col-2"
|
||||
v-model="data.lehrveranstaltung_id"
|
||||
name="lehrveranstaltung_id"
|
||||
/>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'unr')"
|
||||
type="text"
|
||||
container-class="col-1"
|
||||
v-model="data.unr"
|
||||
name="unr"
|
||||
/>
|
||||
<form-input
|
||||
v-if="showGewichtung"
|
||||
:label="$p.t('lehre', 'gewicht')"
|
||||
type="text"
|
||||
container-class="col-1"
|
||||
v-model="data.gewicht"
|
||||
name="gewicht"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -50,7 +50,7 @@ export default{
|
||||
let button = document.createElement('button');
|
||||
container.className = "d-flex gap-1";
|
||||
|
||||
button.className = 'btn btn-outline-secondary btn-action';
|
||||
button.className = 'btn btn-outline-secondary';
|
||||
button.innerHTML = '<i class="fa fa-xmark"></i>';
|
||||
button.title = this.$p.t('ui', 'loeschen');
|
||||
button.addEventListener('click', (event) => {
|
||||
@@ -63,7 +63,7 @@ export default{
|
||||
{
|
||||
button = document.createElement('button');
|
||||
container.className = "d-flex gap-2";
|
||||
button.className = 'btn btn-outline-secondary btn-action';
|
||||
button.className = 'btn btn-outline-secondary';
|
||||
button.innerHTML = '<i class="fa fa-calendar-xmark"></i>';
|
||||
button.title = this.$p.t('lehre', 'auslvplanentfernen');
|
||||
button.disabled = !cell.getData().verplant;
|
||||
|
||||
@@ -32,13 +32,7 @@ export default {
|
||||
studiensemester_kurzbz: { type: String, required: false, default: null },
|
||||
emp: { type: String, required: false, default: null }
|
||||
},
|
||||
computed: {
|
||||
selectedStudiensemester() {
|
||||
return this.studiensemester_kurzbz != null
|
||||
? this.studiensemester_kurzbz
|
||||
: this.defaultSemester;
|
||||
}
|
||||
},
|
||||
|
||||
provide() {
|
||||
return {
|
||||
currentSemester: Vue.computed(() => this.selectedStudiensemester),
|
||||
@@ -70,12 +64,16 @@ export default {
|
||||
emp() {
|
||||
this.updateFilter();
|
||||
},
|
||||
studiensemester_kurzbz(newVal) {
|
||||
this.selectedStudiensemester = newVal ?? this.defaultSemester;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
studiengang: "",
|
||||
filter: {},
|
||||
selectedStudiensemester: this.studiensemester_kurzbz ?? this.defaultSemester,
|
||||
endpoint: ApiStudiengangTree,
|
||||
dropdowns: {
|
||||
studiensemester_array: [],
|
||||
@@ -88,10 +86,12 @@ export default {
|
||||
},
|
||||
selectedStudiengang: '',
|
||||
searchbaroptions: {
|
||||
origin: 'lvverwaltung',
|
||||
cssclass: "position-relative",
|
||||
calcheightonly: true,
|
||||
types: [
|
||||
"mitarbeiter"
|
||||
"mitarbeiter",
|
||||
"mitarbeiter_ohne_zuordnung"
|
||||
],
|
||||
actions: {
|
||||
employee: {
|
||||
|
||||
@@ -121,9 +121,9 @@ export default{
|
||||
}
|
||||
this.$refs.form.call(ApiLektor.update(updatedData))
|
||||
.then(result => {
|
||||
let error = result.data?.error;
|
||||
if (error)
|
||||
this.$fhcAlert.alertWarning(error)
|
||||
let warning = result.data?.retval?.warning;
|
||||
if (warning)
|
||||
this.$fhcAlert.alertWarning(warning)
|
||||
this.original = {...this.data};
|
||||
|
||||
if (this.changed.mitarbeiter_uid)
|
||||
@@ -192,12 +192,13 @@ export default{
|
||||
container-class="col-3"
|
||||
dropdown
|
||||
@complete="searchLektor"
|
||||
name="lektorautocomplete"
|
||||
></form-input>
|
||||
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'anmerkung')"
|
||||
type="text"
|
||||
container-class="col-3"
|
||||
container-class="col-6"
|
||||
v-model="data.anmerkung"
|
||||
name="anmerkung"
|
||||
>
|
||||
@@ -207,7 +208,9 @@ export default{
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'las')"
|
||||
type="text"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
container-class="col-3"
|
||||
:disabled="data.vertrag?.vertragsstatus_kurzbz === 'akzeptiert'"
|
||||
v-model="data.semesterstunden"
|
||||
@@ -217,7 +220,9 @@ export default{
|
||||
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'planstunden')"
|
||||
type="text"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
container-class="col-3"
|
||||
v-model="data.planstunden"
|
||||
name="planstunden"
|
||||
@@ -225,13 +230,15 @@ export default{
|
||||
</form-input>
|
||||
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="row mb-3 d-flex align-items-end">
|
||||
|
||||
<form-input
|
||||
:label="data.default_stundensatz !== null
|
||||
? $p.t('lehre', 'stundensatz') + ' (' + $p.t('lehre', 'default') + ': ' + data.default_stundensatz + ')'
|
||||
: $p.t('lehre', 'stundensatz')"
|
||||
type="text"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
container-class="col-3"
|
||||
v-model="data.stundensatz"
|
||||
:disabled="data.vertrag?.vertragsstatus_kurzbz === 'akzeptiert'"
|
||||
|
||||
@@ -85,7 +85,7 @@ export default{
|
||||
button.className = 'btn btn-outline-secondary btn-action';
|
||||
button.innerHTML = '<i class="fa fa-calendar-xmark"></i>';
|
||||
button.disabled = !cell.getData().verplant;
|
||||
button.title = this.$p.t('ui', 'auslvplanentfernen');
|
||||
button.title = this.$p.t('lehre', 'auslvplanentfernen');
|
||||
button.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
this.deleteLVPlan(cell.getData().mitarbeiter_uid, cell.getData().lehreinheit_id)
|
||||
@@ -199,7 +199,7 @@ export default{
|
||||
table-only
|
||||
:side-menu="false"
|
||||
reload
|
||||
:new-btn-label="$p.t('lehre', 'addlektor')"
|
||||
:new-btn-label="$p.t('lehre', 'addLektor')"
|
||||
new-btn-show
|
||||
@click:new="showAutocomplete = !showAutocomplete"
|
||||
>
|
||||
|
||||
@@ -88,8 +88,14 @@ export default{
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
|
||||
cancelVertrag()
|
||||
async cancelVertrag()
|
||||
{
|
||||
|
||||
if (await this.$fhcAlert.confirm({
|
||||
message: this.$p.t('lehre', 'vertragConfirm'),
|
||||
acceptLabel: this.$p.t('ui', 'ja').charAt(0).toUpperCase() + this.$p.t('ui', 'ja').slice(1),
|
||||
acceptClass: 'btn btn-danger'}) === false)
|
||||
return;
|
||||
let needUpdate = {
|
||||
vertrag_id: this.data.vertrag.vertrag_id,
|
||||
mitarbeiter_uid: this.mitarbeiter_uid,
|
||||
@@ -103,7 +109,6 @@ export default{
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
},
|
||||
// language=HTML
|
||||
template: `
|
||||
<core-form ref="form">
|
||||
<fieldset class="overflow-hidden" v-if="showVertragsdetails">
|
||||
@@ -126,6 +131,7 @@ export default{
|
||||
type="button"
|
||||
class="btn btn-outline-secondary w-100"
|
||||
@click="cancelVertrag"
|
||||
:title="$p.t('lehre', 'cancelvertrag')"
|
||||
>
|
||||
<i class="fa-solid fa-ban"></i>
|
||||
</button>
|
||||
|
||||
@@ -56,7 +56,13 @@ export default {
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
|
||||
},
|
||||
currentSemester: {
|
||||
handler(newVal)
|
||||
{
|
||||
this.lv_info_default.studiensemester_kurzbz = newVal
|
||||
this.lv_info = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -137,6 +143,8 @@ export default {
|
||||
selectableRows: true,
|
||||
rowContextMenu: (component, e) => {
|
||||
|
||||
if (e.getData()?.lehreinheit_id === undefined)
|
||||
return;
|
||||
return [
|
||||
{
|
||||
label: "LV-Teil kopieren",
|
||||
@@ -159,7 +167,7 @@ export default {
|
||||
label: "Nur mit Gruppen",
|
||||
action: (e, row) =>
|
||||
{
|
||||
this.copyLehreinheit(row, "halb");
|
||||
this.copyLehreinheit(row, "gruppen");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -233,6 +241,17 @@ export default {
|
||||
const renderTags = () => {
|
||||
container.innerHTML = '';
|
||||
parsedTags = parsedTags.filter(item => item !== null);
|
||||
|
||||
parsedTags.sort((a, b) => {
|
||||
let adone = a.done ? 1 : 0;
|
||||
let bbone = b.done ? 1 : 0;
|
||||
|
||||
if (adone !== bbone)
|
||||
{
|
||||
return adone - bbone;
|
||||
}
|
||||
return b.id - a.id;
|
||||
});
|
||||
const tagsToShow = rowData._tagExpanded ? parsedTags : parsedTags.slice(0, maxVisibleTags);
|
||||
|
||||
tagsToShow.forEach(tag => {
|
||||
@@ -445,7 +464,7 @@ export default {
|
||||
if (!tagExists)
|
||||
{
|
||||
addedTag.id = tag.id;
|
||||
tags.push({ ...addedTag });
|
||||
tags.unshift({ ...addedTag });
|
||||
targetRow.update({ tags: JSON.stringify(tags) });
|
||||
targetRow.reformat();
|
||||
}
|
||||
@@ -659,19 +678,19 @@ export default {
|
||||
<slot name="filterzuruecksetzen"></slot>
|
||||
</template>
|
||||
</core-filter-cmpt>
|
||||
<bs-modal ref="lehreinheitModal" dialogClass="modal-lg">
|
||||
<template #title>
|
||||
<p class="fw-bold mt-3">{{$p.t('lehre', 'newlehreinheit')}}</p>
|
||||
</template>
|
||||
<bs-modal ref="lehreinheitModal" dialogClass="modal-xxl">
|
||||
<template #title>
|
||||
<p class="fw-bold mt-3">{{$p.t('lehre', 'newlehreinheit')}}</p>
|
||||
</template>
|
||||
|
||||
<template v-if="lv_info">
|
||||
<details-form :data="lv_info"/>
|
||||
</template>
|
||||
<template v-if="lv_info">
|
||||
<details-form :data="lv_info"/>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-primary" @click="addNewLehreinheit">{{$p.t('ui', 'speichern')}}</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-primary" @click="addNewLehreinheit">{{$p.t('ui', 'speichern')}}</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
|
||||
`
|
||||
};
|
||||
@@ -50,7 +50,6 @@ export default {
|
||||
:modelValue="lv[0]"
|
||||
:config="configLVTabs"
|
||||
:default="$route.params.tab"
|
||||
style="flex: 1 1 0%; height: 0%"
|
||||
@changed="reload"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -111,23 +111,14 @@ export default {
|
||||
<button type="submit" class="btn btn-primary position-absolute top-0 end-0" :disabled="!changedLength">{{$p.t('ui', 'speichern')}}</button>
|
||||
</div>
|
||||
<fieldset class="overflow-hidden">
|
||||
<legend>{{this.$p.t('lehre', 'lehreinheit')}}</legend>
|
||||
<div class="core-header d-flex flex-column w-100 overflow-auto pb-3 gap-0">
|
||||
<legend>{{ this.$p.t('lehre', 'lehreinheit') }}</legend>
|
||||
<small v-if="data" v-html="data.lehreinheit_id" class="text-muted"></small>
|
||||
</div>
|
||||
<template v-if="data">
|
||||
<details-form :data="data"/>
|
||||
</template>
|
||||
</fieldset>
|
||||
</core-form>
|
||||
<fieldset class="overflow-hidden">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<legend>{{this.$p.t('lehre', 'gruppen')}}</legend>
|
||||
<gruppen-table ref="gruppen_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-table>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<legend>{{this.$p.t('lehre', 'assignedPersons')}}</legend>
|
||||
<gruppen-direkt-table ref="gruppen_direkt_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-direkt-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>`
|
||||
`
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import GruppenTable from '../Details/Gruppen.js';
|
||||
import GruppenDirektTable from '../Details/Direktinskription.js';
|
||||
|
||||
export default {
|
||||
name: "LVTabGruppen",
|
||||
components: {
|
||||
GruppenTable,
|
||||
GruppenDirektTable,
|
||||
},
|
||||
props: {
|
||||
modelValue: Object,
|
||||
config: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
dropdowns: {
|
||||
from: 'dropdowns'
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
|
||||
<fieldset class="overflow-hidden">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<legend>{{this.$p.t('lehre', 'gruppen')}}</legend>
|
||||
<gruppen-table ref="gruppen_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-table>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<legend>{{this.$p.t('lehre', 'assignedPersons')}}</legend>
|
||||
<gruppen-direkt-table ref="gruppen_direkt_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-direkt-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
`
|
||||
};
|
||||
@@ -295,7 +295,7 @@ export default {
|
||||
showType_id: false,
|
||||
showId: false,
|
||||
showLastupdate: false
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -464,7 +464,6 @@ export default {
|
||||
});
|
||||
},
|
||||
initTinyMCE() {
|
||||
|
||||
const vm = this;
|
||||
tinymce.init({
|
||||
target: this.$refs.editor.$refs.input, //Important: not selector: to enable multiple import of component
|
||||
@@ -502,15 +501,15 @@ export default {
|
||||
const columnToShow = "show" + column.charAt(0).toUpperCase() + column.slice(1);
|
||||
this.showVariables[columnToShow] = true;
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initializeShowVariables();
|
||||
this.getUid();
|
||||
},
|
||||
async mounted() {
|
||||
if(this.showTinyMce){
|
||||
this.initTinyMCE();
|
||||
if (this.showTinyMce) {
|
||||
await this.initTinyMCE();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -548,16 +547,17 @@ export default {
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if(this.showTinyMce) {
|
||||
this.editor.destroy();
|
||||
beforeUnmount() {
|
||||
if (this.editor && tinymce.get(this.editor.id)) {
|
||||
tinymce.get(this.editor.id).remove();
|
||||
this.editor = null;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="core-notiz">
|
||||
|
||||
<div v-if="notizLayout=='classicFas'">
|
||||
|
||||
|
||||
<core-filter-cmpt
|
||||
ref="table"
|
||||
:tabulator-options="tabulatorOptions"
|
||||
|
||||
@@ -318,16 +318,6 @@ export default {
|
||||
</form-input>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
v-if="!config.hiddenFields.includes('svnr')"
|
||||
container-class="col-4 stv-details-details-svnr"
|
||||
:label="$p.t('person', 'svnr')"
|
||||
type="text"
|
||||
v-model="data.svnr"
|
||||
name="svnr"
|
||||
maxlength="16"
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!config.hiddenFields.includes('ersatzkennzeichen')"
|
||||
container-class="col-4 stv-details-details-ersatzkennzeichen"
|
||||
|
||||
@@ -17,21 +17,23 @@ export default {
|
||||
},
|
||||
template: `
|
||||
<div class="stv-details-notizen h-100 pb-3">
|
||||
<!-- mit factory als endpoint -->
|
||||
<core-notiz
|
||||
class="overflow-hidden"
|
||||
:endpoint="endpoint"
|
||||
ref="formc"
|
||||
notiz-layout="popupModal"
|
||||
type-id="person_id"
|
||||
:id="modelValue.person_id"
|
||||
show-document
|
||||
show-tiny-mce
|
||||
:visible-columns="['titel','text','verfasser','bearbeiter','dokumente']"
|
||||
>
|
||||
</core-notiz>
|
||||
|
||||
|
||||
|
||||
<!-- Test Version classicFas for enter with one click vs popupModal-->
|
||||
<core-notiz
|
||||
class="overflow-hidden"
|
||||
:endpoint="endpoint"
|
||||
ref="formc"
|
||||
notiz-layout="popupModal"
|
||||
type-id="person_id"
|
||||
:id="modelValue.person_id"
|
||||
show-document
|
||||
show-tiny-mce
|
||||
:visibleColumns="['titel','text','verfasser','bearbeiter','dokumente']"
|
||||
@reload="$emit('update:suffix')"
|
||||
>
|
||||
</core-notiz>
|
||||
|
||||
<!--
|
||||
---------------------------------------------------------------------------------------------
|
||||
-------------------- DESCRIPTION FOR PARAMETER PROPS ----------------------------------------
|
||||
|
||||
@@ -146,14 +146,25 @@ export default {
|
||||
this.addStudent({status_kurzbz: 'student', statusgrund_id});
|
||||
},
|
||||
addStudent(data) {
|
||||
Promise
|
||||
.allSettled(
|
||||
this.prestudentIds.map(prestudent_id => this.$api.call(
|
||||
ApiStvStatus.addStudent(prestudent_id, data),
|
||||
{ errorHeader: prestudent_id }
|
||||
))
|
||||
)
|
||||
.then(res => this.showFeedback(res, data.status_kurzbz));
|
||||
this.$api.call(this.prestudentIds.map(prestudent_id => [
|
||||
prestudent_id,
|
||||
ApiStvStatus.addStudent(prestudent_id, data),
|
||||
{ errorHeader: prestudent_id }
|
||||
]))
|
||||
.then(result => {
|
||||
const messagesSuccessful = result.filter(res => res.status == 'fulfilled');
|
||||
if (messagesSuccessful.length) {
|
||||
this.$fhcAlert.alertDefault(
|
||||
'info',
|
||||
'Feedback',
|
||||
messagesSuccessful.length + " erfolgreiche Statusänderung(en) durchgeführt", // TODO(chris): translate
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
this.$emit('reloadTable');
|
||||
this.$reloadList();
|
||||
});
|
||||
},
|
||||
changeStatusToAbbrecher(statusgrund_id) {
|
||||
this
|
||||
@@ -247,31 +258,15 @@ export default {
|
||||
},
|
||||
changeStatus(data) {
|
||||
data.currentSemester = this.currentSemester;
|
||||
Promise
|
||||
.allSettled(
|
||||
this.prestudentIds.map(prestudent_id => this.$api.call(
|
||||
ApiStvStatus.changeStatus(prestudent_id, data),
|
||||
{ errorHeader: prestudent_id }
|
||||
))
|
||||
)
|
||||
.then(res => this.showFeedback(res, data.status_kurzbz));
|
||||
},
|
||||
showFeedback(results, status_kurzbz) {
|
||||
const countSuccess = results.filter(result => result.status == "fulfilled").length;
|
||||
const countError = results.length - countSuccess;
|
||||
|
||||
//Feedback Success als infoalert
|
||||
this.$fhcAlert.alertInfo(this.$p.t('ui', 'successNewStatus', {
|
||||
countSuccess,
|
||||
status: status_kurzbz,
|
||||
countError
|
||||
}));
|
||||
|
||||
if(results.length == 1 && countSuccess > 0){
|
||||
this.$api.call(this.prestudentIds.map(prestudent_id => [
|
||||
prestudent_id,
|
||||
ApiStvStatus.changeStatus(prestudent_id, data)
|
||||
]))
|
||||
.then(() => {
|
||||
this.$emit('reloadTable');
|
||||
}
|
||||
this.$reloadList();
|
||||
}
|
||||
this.$reloadList();
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$api
|
||||
@@ -359,4 +354,4 @@ export default {
|
||||
</ul>
|
||||
</div>
|
||||
</div>`
|
||||
};
|
||||
};
|
||||
|
||||
@@ -50,7 +50,6 @@ export default {
|
||||
{title:"Wahlname", field:"wahlname", visible:false, headerFilter: true},
|
||||
{title:"Vornamen", field:"vornamen", visible:false, headerFilter: true},
|
||||
{title:"TitelPost", field:"titelpost", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
|
||||
{title:"SVNR", field:"svnr", headerFilter: true},
|
||||
{title:"Ersatzkennzeichen", field:"ersatzkennzeichen", headerFilter: true},
|
||||
{title:"Geburtsdatum", field:"gebdatum", formatter:dateFormatter,
|
||||
headerFilter: true, headerFilterFunc: function(headerValue, rowValue, rowData, filterParams) {
|
||||
|
||||
@@ -228,7 +228,7 @@ export default {
|
||||
},
|
||||
template: `
|
||||
<fhc-form ref="form" class="stv-list-new" @submit.prevent="send">
|
||||
<bs-modal ref="modal" dialog-class="modal-lg modal-scrollable" @hidden-bs-modal="reset">
|
||||
<bs-modal ref="modal" dialog-class="modal-lg modal-dialog-scrollable" @hidden-bs-modal="reset">
|
||||
<template #title>
|
||||
{{ personOnly ? $p.t('person', 'personAnlegen') : $p.t('lehre', 'interessentAnlegen') }}
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import {CoreRESTClient} from '../../../RESTClient.js';
|
||||
|
||||
|
||||
import PvTree from "../../../../../index.ci.php/public/js/components/primevue/tree/tree.esm.min.js";
|
||||
import PvTreetable from "../../../../../index.ci.php/public/js/components/primevue/treetable/treetable.esm.min.js";
|
||||
import PvColumn from "../../../../../index.ci.php/public/js/components/primevue/column/column.esm.min.js";
|
||||
import ApiStvVerband from '../../../api/factory/stv/verband.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PvTree,
|
||||
PvTreetable,
|
||||
PvColumn
|
||||
PvTreetable: primevue.treetable,
|
||||
PvColumn: primevue.column
|
||||
},
|
||||
emits: [
|
||||
'selectVerband'
|
||||
@@ -31,14 +25,15 @@ export default {
|
||||
selectedKey: [],
|
||||
expandedKeys: {},
|
||||
filters: {}, // TODO(chris): filter only 1st level?
|
||||
favnodes: [],
|
||||
favorites: {on: false, list: []}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredNodes() {
|
||||
// TODO(chris): what to display actually?
|
||||
return this.favorites.on ? this.favnodes : this.nodes;
|
||||
if (this.favorites.on)
|
||||
return this.nodes.filter(node => this.favorites.list.includes(node.key));
|
||||
|
||||
return this.nodes;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -118,69 +113,49 @@ export default {
|
||||
return cp;
|
||||
},
|
||||
async filterFav() {
|
||||
if (!this.favorites.on && !this.favnodes.length && this.favorites.list.length) {
|
||||
this.loading = true;
|
||||
this.favnodes = await this.loadNodes(this.favorites.list);
|
||||
}
|
||||
this.favorites.on = !this.favorites.on;
|
||||
this.$api
|
||||
.call(this.endpoint.favorites.set(
|
||||
JSON.stringify(this.favorites)
|
||||
));
|
||||
this.loading = false;
|
||||
},
|
||||
async loadNodes(links) {
|
||||
let sortedInParents = links.reduce((o, link) => {
|
||||
link = link + '';
|
||||
let parent,
|
||||
parts = link.split('/');
|
||||
if (parts.length == 1) {
|
||||
parent = '_';
|
||||
} else {
|
||||
parts.pop();
|
||||
parent = parts.join('/');
|
||||
}
|
||||
if (!o[parent])
|
||||
o[parent] = [link];
|
||||
else
|
||||
o[parent].push(link);
|
||||
return o;
|
||||
}, {});
|
||||
|
||||
let promises = [];
|
||||
for (let parent in sortedInParents)
|
||||
promises.push(
|
||||
this.$api
|
||||
.call(this.endpoint.get(parent == '_' ? '' : parent))
|
||||
.then(res => res.data)
|
||||
.then(res => res.filter(node => sortedInParents[parent].includes(node.link + '')))
|
||||
);
|
||||
|
||||
// NOTE(chris): merge the resulting arrays and transform them to an associative one
|
||||
let result = [].concat.apply([], await Promise.all(promises)).reduce((o, node) => {
|
||||
o[node.link + ''] = this.mapResultToTreeData({...node, leaf: true, children: undefined});
|
||||
return o;
|
||||
}, {});
|
||||
|
||||
return links.map(link => result[link]);
|
||||
))
|
||||
.then(result => {
|
||||
if (result.meta?.removed) {
|
||||
this.favorites.list = this.favorites.list
|
||||
.filter(fav => !result.meta.removed.includes(fav));
|
||||
const items = result.meta.removed.map(
|
||||
rem => this.nodes.find(
|
||||
node => node.data.link == rem
|
||||
).label
|
||||
).join(',\n');
|
||||
this.$fhcAlert.alertWarning(this.$p.t('stv/warn_removed_favs', { items }));
|
||||
}
|
||||
});
|
||||
},
|
||||
async markFav(key) {
|
||||
let index = this.favorites.list.indexOf(key.data.link + '');
|
||||
|
||||
if (index != -1) {
|
||||
if (this.favnodes.length)
|
||||
this.favnodes = this.favnodes.filter(node => node.data.link != key.data.link);
|
||||
this.favorites.list.splice(index, 1);
|
||||
} else {
|
||||
if (this.favnodes.length || this.favorites.on)
|
||||
this.favnodes.push((await this.loadNodes([key.data.link])).pop());
|
||||
this.favorites.list.push(key.data.link + '');
|
||||
}
|
||||
|
||||
this.$api
|
||||
.call(this.endpoint.favorites.set(
|
||||
JSON.stringify(this.favorites)
|
||||
));
|
||||
))
|
||||
.then(result => {
|
||||
if (result.meta?.removed) {
|
||||
this.favorites.list = this.favorites.list
|
||||
.filter(fav => !result.meta.removed.includes(fav));
|
||||
const items = "\n" + result.meta.removed.map(
|
||||
rem => this.nodes.find(
|
||||
node => node.data.link == rem
|
||||
).label
|
||||
).join(",\n");
|
||||
this.$fhcAlert.alertWarning(this.$p.t('stv/warn_removed_favs', { items }));
|
||||
}
|
||||
});
|
||||
},
|
||||
unsetFavFocus(e) {
|
||||
if (e.target.dataset?.linkFavAdd !== undefined) {
|
||||
@@ -215,6 +190,24 @@ export default {
|
||||
if (!currentNode)
|
||||
return;
|
||||
|
||||
const currentSelectedKey = Object.keys(this.selectedKey).find(Boolean);
|
||||
if (currentSelectedKey) {
|
||||
if (currentSelectedKey == currentKey)
|
||||
return;
|
||||
/**
|
||||
* Do not select a new entry if the current is a child of the new one.
|
||||
* This happens if a child entry of a new stg is selected and the router
|
||||
* tries to select the stg root entry (because subtrees do not have
|
||||
* routes yet)
|
||||
*/
|
||||
const isChild = this.findNodeByKey(
|
||||
currentSelectedKey,
|
||||
currentNode.children
|
||||
);
|
||||
if (isChild)
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 1; i < parts.length; i++)
|
||||
{
|
||||
this.expandedKeys[currentNode.key] = true;
|
||||
@@ -238,7 +231,10 @@ export default {
|
||||
this.$api
|
||||
.call(this.endpoint.get())
|
||||
.then(result => {
|
||||
this.nodes = result.data.map(this.mapResultToTreeData);
|
||||
this.nodes = result.data.map(el => {
|
||||
el.root = true;
|
||||
return this.mapResultToTreeData(el);
|
||||
});
|
||||
this.setPreselection();
|
||||
this.loading = false;
|
||||
})
|
||||
@@ -248,21 +244,12 @@ export default {
|
||||
.call(this.endpoint.favorites.get())
|
||||
.then(result => {
|
||||
if (result.data) {
|
||||
let f = JSON.parse(result.data);
|
||||
if (f.on) {
|
||||
this.loading = true;
|
||||
this.favorites = f;
|
||||
this.loadNodes(this.favorites.list).then(res => {
|
||||
this.favnodes = res;
|
||||
this.loading = false;
|
||||
});
|
||||
} else
|
||||
this.favorites = f;
|
||||
this.favorites = JSON.parse(result.data);
|
||||
}
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
template: `
|
||||
template: /* html */`
|
||||
<div class="overflow-auto" tabindex="-1">
|
||||
<pv-treetable
|
||||
ref="tree"
|
||||
@@ -278,39 +265,68 @@ export default {
|
||||
@focusin="setFavFocus"
|
||||
@focusout="unsetFavFocus"
|
||||
:filters="filters"
|
||||
>
|
||||
<pv-column
|
||||
field="name"
|
||||
expander
|
||||
class="text-break"
|
||||
>
|
||||
<pv-column field="name" expander>
|
||||
<template #header>
|
||||
<div class="text-right">
|
||||
<div class="p-input-icon-left">
|
||||
<i class="pi pi-search"></i>
|
||||
<input type="text" v-model="filters['global']" class="form-control ps-5" placeholder="Search" />
|
||||
<input
|
||||
type="text"
|
||||
v-model="filters['global']"
|
||||
class="form-control ps-5"
|
||||
placeholder="Search"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #body="{node}">
|
||||
<span :data-tree-item-key="node.key" :title="node.data.studiengang_kz">
|
||||
<template #body="{ node }">
|
||||
<span
|
||||
:data-tree-item-key="node.key"
|
||||
:title="node.data.studiengang_kz"
|
||||
>
|
||||
{{node.data.name}}
|
||||
</span>
|
||||
</template>
|
||||
</pv-column>
|
||||
<pv-column field="fav" headerStyle="flex: 0 0 auto" style="flex: 0 0 auto">
|
||||
<pv-column
|
||||
field="fav"
|
||||
class="flex-shrink-0 flex-grow-0"
|
||||
header-class="flex-shrink-0 flex-grow-0"
|
||||
>
|
||||
<template #header>
|
||||
<a href="#" @click.prevent="filterFav"><i :class="favorites.on ? 'fa-solid' : 'fa-regular'" class="fa-star"></i></a>
|
||||
</template>
|
||||
<template #body="{node, column}">
|
||||
<a
|
||||
v-if="favorites.on || favorites.list.length"
|
||||
href="#"
|
||||
@click.prevent="filterFav"
|
||||
>
|
||||
<i
|
||||
:class="favorites.on ? 'fa-solid' : 'fa-regular'"
|
||||
class="fa-star"
|
||||
></i>
|
||||
</a>
|
||||
</template>
|
||||
<template #body="{ node }">
|
||||
<a
|
||||
v-if="node.data.root"
|
||||
href="#"
|
||||
@click.prevent="markFav(node)"
|
||||
@keydown.enter.stop.prevent="markFav(node)"
|
||||
tabindex="-1"
|
||||
data-link-fav-add
|
||||
>
|
||||
<i :class="favorites.list.includes(node.data.link + '') ? 'fa-solid' : 'fa-regular'" class="fa-star"></i>
|
||||
@click.prevent="markFav(node)"
|
||||
@keydown.enter.stop.prevent="markFav(node)"
|
||||
>
|
||||
<i
|
||||
:class="favorites.list.includes(node.data.link + '') ? 'fa-solid' : 'fa-regular'"
|
||||
class="fa-star"
|
||||
></i>
|
||||
</a>
|
||||
</template>
|
||||
</pv-column>
|
||||
<pv-column field="studiengang_kz" class="d-none"></pv-column>
|
||||
</pv-treetable>
|
||||
</div>`
|
||||
};
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ export default {
|
||||
currentTab() {
|
||||
if (this.tabs[this.current])
|
||||
return this.tabs[this.current];
|
||||
|
||||
|
||||
return { component: 'div' };
|
||||
},
|
||||
value: {
|
||||
@@ -67,9 +67,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTabClick: function(index) {
|
||||
handleTabClick: function (e) {
|
||||
let keys = Object.keys(this.tabs);
|
||||
this.change(keys[index]);
|
||||
this.change(keys[e.index]);
|
||||
},
|
||||
change(key) {
|
||||
this.$emit("change", key)
|
||||
@@ -97,13 +97,20 @@ export default {
|
||||
if (!item.component)
|
||||
return console.error('Component missing for ' + key);
|
||||
|
||||
//making it reactive for showing headerSuffix
|
||||
const value = Vue.reactive({
|
||||
suffix: '',
|
||||
showSuffix: item.showSuffix || false
|
||||
});
|
||||
|
||||
tabs[key] = {
|
||||
component: Vue.markRaw(Vue.defineAsyncComponent(() => import(item.component))),
|
||||
title: Vue.computed(() => item.title || key),
|
||||
config: item.config,
|
||||
key,
|
||||
value: {}
|
||||
}
|
||||
value,
|
||||
suffixhelper: item.suffixhelper ?? null
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(config))
|
||||
@@ -118,36 +125,67 @@ export default {
|
||||
this.current = Object.keys(tabs)[0];
|
||||
}
|
||||
this.tabs = tabs;
|
||||
},
|
||||
updateSuffix() {
|
||||
this.getTabSuffix(this.currentTab);
|
||||
},
|
||||
async getTabSuffix(tab) {
|
||||
if (!tab.value.showSuffix) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab.suffixhelper !== null) {
|
||||
const suffixhelper = await import(tab.suffixhelper);
|
||||
const suffix = await suffixhelper.getSuffix(this.$api, this.modelValue);
|
||||
tab.value.suffix = suffix;
|
||||
} else {
|
||||
tab.value.suffix = '';
|
||||
}
|
||||
},
|
||||
getTabSuffixes() {
|
||||
Object.entries(this.tabs).forEach(([key, item]) => this.getTabSuffix(item));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initConfig(this.config);
|
||||
},
|
||||
mounted() {
|
||||
this.getTabSuffixes();
|
||||
},
|
||||
updated() {
|
||||
this.getTabSuffixes();
|
||||
},
|
||||
template: `
|
||||
<template v-if="useprimevue">
|
||||
|
||||
<tabview
|
||||
:scrollable="true"
|
||||
:lazy="true"
|
||||
:activeIndex="calcActiveIndex"
|
||||
@tab-click="handleTabClick"
|
||||
>
|
||||
<tabpanel
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:header="tab.title"
|
||||
<tabview
|
||||
:scrollable="true"
|
||||
:lazy="true"
|
||||
:activeIndex="calcActiveIndex"
|
||||
@tab-click="handleTabClick"
|
||||
>
|
||||
<keep-alive>
|
||||
<component :is="tab.component" v-model="value" :config="tab.config"></component>
|
||||
</keep-alive>
|
||||
</tabpanel>
|
||||
</tabview>
|
||||
<tabpanel
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:header="tab.title + ((tab.value.showSuffix && tab.value.suffix !== '') ? ' ' + tab.value.suffix : '')"
|
||||
>
|
||||
<keep-alive>
|
||||
<component
|
||||
:is="tab.component"
|
||||
v-model="value"
|
||||
:config="tab.config"
|
||||
@update:suffix="updateSuffix($event)"
|
||||
></component>
|
||||
</keep-alive>
|
||||
</tabpanel>
|
||||
</tabview>
|
||||
|
||||
</template>
|
||||
<template v-else="">
|
||||
|
||||
<div class="fhc-tabs d-flex" :class="vertical ? 'align-items-stretch gap-3' : (border ? 'flex-column' : 'flex-column gap-3')" v-if="Object.keys(tabs).length">
|
||||
<div class="nav" :class="vertical ? 'nav-pills flex-column' : 'nav-tabs'">
|
||||
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
@@ -157,15 +195,22 @@ export default {
|
||||
:aria-current="tab.key == current ? 'page' : ''"
|
||||
v-accessibility:tab.[vertical]
|
||||
>
|
||||
{{tab.title}}
|
||||
{{tab.title}} <span v-if="tab.value.showSuffix && tab.value.suffix"> {{ tab.value.suffix }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :style="vertical ? '' : 'flex: 1 1 0%; height: 0%'" class="overflow-auto flex-grow-1" :class="vertical || !border ? '' : 'p-3 border-bottom border-start border-end'">
|
||||
<keep-alive>
|
||||
<component ref="current" :is="currentTab.component" v-model="value" :config="currentTab.config"></component>
|
||||
<component
|
||||
ref="current"
|
||||
:is="currentTab.component"
|
||||
v-model="value"
|
||||
:config="currentTab.config"
|
||||
@update:suffix="updateSuffix($event)"
|
||||
></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>`
|
||||
};
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ export default {
|
||||
this.tagData.updateamum = this.formatDateTime(item.updateamum)
|
||||
this.tagData.bearbeiter = item.bearbeiter;
|
||||
this.tagData.verfasser = item.verfasser;
|
||||
this.tagData.readonly = item.readonly;
|
||||
|
||||
if (item && item.notiz_id)
|
||||
{
|
||||
@@ -154,7 +155,8 @@ export default {
|
||||
|
||||
let postData = {
|
||||
id: this.selectedTagId,
|
||||
done: !this.tagData.done
|
||||
done: !this.tagData.done,
|
||||
notiz: this.tagData.notiz,
|
||||
}
|
||||
this.$api.call(this.endpoint.doneTag(postData))
|
||||
this.$emit("updated", this.tagData);
|
||||
@@ -182,7 +184,8 @@ export default {
|
||||
verfasser: "",
|
||||
updateamum: "",
|
||||
bearbeiter: "",
|
||||
response: ""
|
||||
response: "",
|
||||
readonly: false
|
||||
};
|
||||
this.selectedTagId = null;
|
||||
this.mode = "create";
|
||||
@@ -230,6 +233,7 @@ export default {
|
||||
v-model="tagData.notiz"
|
||||
type="textarea"
|
||||
field="notiz"
|
||||
:readonly="tagData.readonly"
|
||||
placeholder="Notiz..."
|
||||
></form-input>
|
||||
<div class="modificationdate">
|
||||
@@ -243,7 +247,7 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<template #footer v-if="!tagData.readonly">
|
||||
<div class="d-flex justify-content-between w-100">
|
||||
<div>
|
||||
<button
|
||||
|
||||
@@ -95,7 +95,6 @@ export const CoreFilterCmpt = {
|
||||
dataset: null,
|
||||
datasetMetadata: null,
|
||||
selectedFields: null,
|
||||
notSelectedFields: null,
|
||||
filterFields: null,
|
||||
|
||||
availableFilters: null,
|
||||
@@ -107,6 +106,8 @@ export const CoreFilterCmpt = {
|
||||
fetchCmptApiFunctionParams: null,
|
||||
fetchCmptDataFetched: null,
|
||||
|
||||
fetchResult: null,
|
||||
|
||||
tabulator: null,
|
||||
tableBuilt: false,
|
||||
tabulatorHasSelector: false,
|
||||
@@ -122,6 +123,11 @@ export const CoreFilterCmpt = {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
notSelectedFields() {
|
||||
if (!this.fields || !this.selectedFields)
|
||||
return null;
|
||||
return this.fields.filter(x => this.selectedFields.indexOf(x) === -1)
|
||||
},
|
||||
filteredData() {
|
||||
if (!this.dataset)
|
||||
return [];
|
||||
@@ -219,6 +225,32 @@ export const CoreFilterCmpt = {
|
||||
await this.$p.loadCategory('ui');
|
||||
placeholder = this.$p.t('ui/keineDatenVorhanden');
|
||||
}
|
||||
|
||||
if (!this.tableOnly) {
|
||||
// prefetch data to get fields & selectedFields for filteredColumns & filteredData
|
||||
await new Promise(resolve => {
|
||||
const filterId = window.location.hash ? window.location.hash.slice(1) : null;
|
||||
|
||||
const resolvePromiseFunc = data => {
|
||||
this.setRenderData(data);
|
||||
resolve();
|
||||
};
|
||||
// get the filter data
|
||||
if (filterId === null)
|
||||
this.startFetchCmpt(
|
||||
wsParams => this.$api.call(ApiFilter.getFilter(wsParams)),
|
||||
null,
|
||||
resolvePromiseFunc
|
||||
);
|
||||
else
|
||||
this.startFetchCmpt(
|
||||
wsParams => this.$api.call(ApiFilter.getFilterById(wsParams)),
|
||||
{ filterId },
|
||||
resolvePromiseFunc
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Define a default tabulator options in case it was not provided
|
||||
let tabulatorOptions = {...{
|
||||
layout: "fitDataStretchFrozen",
|
||||
@@ -240,6 +272,11 @@ export const CoreFilterCmpt = {
|
||||
if (!this.tableOnly) {
|
||||
tabulatorOptions.data = this.filteredData;
|
||||
tabulatorOptions.columns = this.filteredColumns;
|
||||
} else {
|
||||
tabulatorOptions.columns.forEach(col => {
|
||||
if (col.visible === undefined)
|
||||
col.visible = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (tabulatorOptions.selectable || (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length))
|
||||
@@ -359,18 +396,14 @@ export const CoreFilterCmpt = {
|
||||
this.render
|
||||
);
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
render(response) {
|
||||
let data = response;
|
||||
setRenderData(data) {
|
||||
this.fetchResult = data;
|
||||
this.filterName = data.filterName;
|
||||
this.dataset = data.dataset;
|
||||
this.datasetMetadata = data.datasetMetadata;
|
||||
|
||||
this.fields = data.fields;
|
||||
this.selectedFields = data.selectedFields;
|
||||
this.notSelectedFields = this.fields.filter(x => this.selectedFields.indexOf(x) === -1);
|
||||
this.filterFields = [];
|
||||
|
||||
for (let i = 0; i < data.datasetMetadata.length; i++)
|
||||
@@ -387,6 +420,14 @@ export const CoreFilterCmpt = {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
render(response) {
|
||||
let data = response;
|
||||
|
||||
this.setRenderData(data);
|
||||
|
||||
// If the side menu is active
|
||||
if (this.sideMenu === true)
|
||||
@@ -627,11 +668,10 @@ export const CoreFilterCmpt = {
|
||||
this.$emit('uuidDefined', this.uuid)
|
||||
},
|
||||
mounted() {
|
||||
|
||||
this.initTabulator().then(() => {
|
||||
if (!this.tableOnly) {
|
||||
this.selectedFilter = window.location.hash ? window.location.hash.slice(1) : null;
|
||||
this.getFilter(); // get the filter data
|
||||
this.render(this.fetchResult);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -666,7 +706,7 @@ export const CoreFilterCmpt = {
|
||||
</button>
|
||||
<span v-if="$slots.actions && tabulatorHasSelector && useSelectionSpan">
|
||||
<span v-if="countOnly">{{ selectedData.length }} ausgewählt</span>
|
||||
<span v-else> Mit {{ selectedData.length }} ausgewählten:</span>
|
||||
<span v-else id="selected-info-text"> Mit {{ selectedData.length }} ausgewählten:</span>
|
||||
</span>
|
||||
<slot name="actions" v-bind="{selected: tabulatorHasSelector ? selectedData : []}"></slot>
|
||||
<slot name="search"></slot>
|
||||
|
||||
@@ -116,7 +116,7 @@ class GridLogic {
|
||||
prefer = DIR_RIGHT;
|
||||
}
|
||||
|
||||
const originalFrame = [...item.frame];
|
||||
const originalFrame = Array.isArray(item.frame) ? [...item.frame] : [item.frame];
|
||||
|
||||
const currItem = {...item};
|
||||
currItem.x = x;
|
||||
@@ -146,6 +146,7 @@ class GridLogic {
|
||||
}
|
||||
}
|
||||
replaceUpdate[item.index] = { index: item.index, x, y };
|
||||
|
||||
return replaceUpdate;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import ApiNotizPerson from '../../../../../api/factory/notiz/person.js';
|
||||
|
||||
export async function getSuffix(api, modelValue) {
|
||||
const response = await api
|
||||
.call(ApiNotizPerson.getCountNotes(modelValue.person_id));
|
||||
const suffix = '(' + response.data + ')';
|
||||
return suffix;
|
||||
}
|
||||
@@ -44,7 +44,6 @@ $(document).ready(function ()
|
||||
"nachname" : $('#nachname_input').val(),
|
||||
"titelpost" : $('#titelpost_input').val(),
|
||||
"gebdatum" : $('#gebdatum_input').val(),
|
||||
"svnr" : $('#svnr_input').val(),
|
||||
"buergerschaft" : $('#buergerschaft').val(),
|
||||
"geschlecht" : $('#geschlecht').val(),
|
||||
"gebnation" : $('#gebnation').val(),
|
||||
|
||||
+418
-184
@@ -10,7 +10,232 @@ export default {
|
||||
if (!app.config.globalProperties.$fhcAlert)
|
||||
app.use(FhcAlert);
|
||||
|
||||
function _get_config(form, uri, data, config) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
function _send_array_or_object(errors, func) {
|
||||
if (Array.isArray(errors))
|
||||
errors.forEach(func);
|
||||
else
|
||||
Object.entries(errors).forEach(
|
||||
([title, errs]) => errs.forEach(
|
||||
error => func(error, title)
|
||||
)
|
||||
);
|
||||
}
|
||||
let DEFAULT_ERROR_CONFIG = {
|
||||
success: true,
|
||||
fail: true,
|
||||
combine: {
|
||||
form: ['validation', 'general'],
|
||||
toast: ['validation', 'general', 'not_found', 'site_failed']
|
||||
},
|
||||
handler: {
|
||||
form(form, errors) {
|
||||
form.clearValidation();
|
||||
errors.forEach(err => form.setFeedback(
|
||||
false,
|
||||
err.messages || err.message
|
||||
));
|
||||
},
|
||||
async toast(errors) {
|
||||
const $p = app.config.globalProperties.$p;
|
||||
if (!$p)
|
||||
return Promise.reject('Phrasen plugin not loaded!');
|
||||
|
||||
async function _format_toast(errors) {
|
||||
errors = errors.reduce((result, err) => {
|
||||
switch (err.type) {
|
||||
case 'not_found':
|
||||
case 'site_failed':
|
||||
if (err.message)
|
||||
result[err.message] = [err.url];
|
||||
else
|
||||
result._default = [err.url];
|
||||
break;
|
||||
case 'general':
|
||||
if (!result._default)
|
||||
result._default = [];
|
||||
result._default.push(err.message);
|
||||
break;
|
||||
case 'validation':
|
||||
Object.entries(err.messages)
|
||||
.forEach(([field, msg]) => {
|
||||
if (!result[field])
|
||||
result[field] = [];
|
||||
if (Array.isArray(msg))
|
||||
result[field].push(...msg);
|
||||
else
|
||||
result[field].push(msg);
|
||||
});
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
let counter = 0;
|
||||
const msgs = await Promise.all(Object.entries(errors)
|
||||
.sort((a, b) => ['_default'].indexOf(b[0]) - ['_default'].indexOf(a[0])) // sort _default first
|
||||
.map(async ([field, msgs]) => {
|
||||
if (field == '_default') {
|
||||
await $p.loadCategory('dashboard');
|
||||
const general = $p.t('dashboard/general');
|
||||
field = '<dt class="d-none">' + general + '</dt>';
|
||||
} else {
|
||||
field = '<dt>' + field + '</dt>';
|
||||
}
|
||||
counter += msgs.length;
|
||||
return field
|
||||
+ '<dd>'
|
||||
+ msgs.join('</dd><dd>')
|
||||
+ '</dd>';
|
||||
}));
|
||||
return {
|
||||
counter,
|
||||
msgs
|
||||
}
|
||||
}
|
||||
|
||||
let counter, msgs;
|
||||
if (Array.isArray(errors)) {
|
||||
({ counter, msgs } = await _format_toast(errors));
|
||||
} else {
|
||||
({ counter, msgs } = await Object.entries(errors)
|
||||
.reduce(async (res, [title, errs]) => {
|
||||
const result = await res;
|
||||
const { counter, msgs } = await _format_toast(errs);
|
||||
result.counter += counter;
|
||||
result.msgs.push('<dt>'
|
||||
+ title
|
||||
+ '</dt><dd><dl>'
|
||||
+ msgs.join('')
|
||||
+ '</dl></dd>');
|
||||
return result;
|
||||
}, Promise.resolve({ counter: 0, msgs: []})));
|
||||
}
|
||||
|
||||
await $p.loadCategory('ui');
|
||||
const n_errors = $p.t('ui/n_errors', { n: counter });
|
||||
|
||||
$fhcAlert.alertDefault(
|
||||
'error',
|
||||
n_errors,
|
||||
'<dl>' + msgs.join('') + '</dl>',
|
||||
true,
|
||||
true
|
||||
);
|
||||
},
|
||||
php(errors) {
|
||||
_send_array_or_object(errors, (error, title) => {
|
||||
var message = '';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
switch (error.severity) {
|
||||
case 'Warning':
|
||||
case 'Core Warning':
|
||||
case 'Compile Warning':
|
||||
case 'User Warning':
|
||||
if (title)
|
||||
title += ': PHP ' + error.severity;
|
||||
else
|
||||
title = 'PHP ' + error.severity;
|
||||
$fhcAlert.alertDefault('warn', title, message, true);
|
||||
break;
|
||||
case 'Notice':
|
||||
case 'User Notice':
|
||||
case 'Runtime Notice':
|
||||
if (title)
|
||||
title += ': PHP ' + error.severity;
|
||||
else
|
||||
title = 'PHP ' + error.severity;
|
||||
$fhcAlert.alertDefault('info', title, message, true);
|
||||
break;
|
||||
default:
|
||||
message = 'Type: PHP ' + error.severity + '\n\n' + message;
|
||||
if (title)
|
||||
message = title + '\n\n' + message;
|
||||
$fhcAlert.alertSystemError(message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
exception(errors) {
|
||||
_send_array_or_object(errors, (error, title) => {
|
||||
var message = '';
|
||||
if (title)
|
||||
message += title + '\n\n';
|
||||
message += 'Type: ' + error.class + '\n\n';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
$fhcAlert.alertSystemError(message);
|
||||
});
|
||||
},
|
||||
db(errors) {
|
||||
_send_array_or_object(errors, (error, title) => {
|
||||
var message = '';
|
||||
if (title)
|
||||
message += title + '\n\n';
|
||||
if (error.heading !== undefined)
|
||||
message += error.heading + '\n\n';
|
||||
if (error.code !== undefined)
|
||||
message += 'Code: ' + error.code + '\n\n';
|
||||
if (error.sql !== undefined)
|
||||
message += 'SQL: ' + error.sql + '\n\n';
|
||||
if (error.message !== undefined)
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
else if (error.messages !== undefined)
|
||||
message += 'Messages: ' + error.messages.join('\n\t') + '\n\n';
|
||||
if (error.filename !== undefined)
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
if (error.line !== undefined)
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
|
||||
$fhcAlert.alertSystemError(message);
|
||||
});
|
||||
},
|
||||
auth(errors) {
|
||||
_send_array_or_object(errors, (error, title) => {
|
||||
if (title)
|
||||
title += ': ' + error.message;
|
||||
else
|
||||
title = error.message;
|
||||
|
||||
var message = '';
|
||||
message += 'Controller name: ' + error.controller + '\n';
|
||||
message += 'Method name: ' + error.method + '\n';
|
||||
message += 'Required permissions: ' + error.required_permissions;
|
||||
|
||||
$fhcAlert.alertDefault(
|
||||
'error',
|
||||
title,
|
||||
message,
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (options?.errorHandling !== undefined)
|
||||
DEFAULT_ERROR_CONFIG = _merge_error_config(options.errorHandling);
|
||||
|
||||
function get_config(form, uri, data, config) {
|
||||
if (typeof form == 'string' && config === undefined) {
|
||||
[uri, data, config] = [form, uri, data];
|
||||
form = undefined;
|
||||
@@ -40,22 +265,125 @@ export default {
|
||||
|
||||
return [uri, data, config];
|
||||
}
|
||||
function clean_return_value(response) {
|
||||
if (typeof response.data === 'string' || response.data instanceof String)
|
||||
return clean_return_value({ data: response });
|
||||
|
||||
function _clean_return_value(response) {
|
||||
const result = response.data;
|
||||
delete response.data;
|
||||
if (!result)
|
||||
return {meta: {response}, data: null};
|
||||
if (!result.meta)
|
||||
result.meta = {response};
|
||||
result.meta = { response };
|
||||
else
|
||||
result.meta.response = response;
|
||||
return result;
|
||||
}
|
||||
const baseURL = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/";
|
||||
function _merge_error_config(config) {
|
||||
if (config === false || config === 'off')
|
||||
return { ...DEFAULT_ERROR_CONFIG, success: false, fail: false };
|
||||
|
||||
if (!config || config === true)
|
||||
return { ...DEFAULT_ERROR_CONFIG };
|
||||
|
||||
if (config === 'success')
|
||||
return { ...DEFAULT_ERROR_CONFIG, fail: false };
|
||||
|
||||
if (config === 'fail')
|
||||
return { ...DEFAULT_ERROR_CONFIG, success: false };
|
||||
|
||||
const { success, fail, handler, combine } = config;
|
||||
|
||||
config = { ...DEFAULT_ERROR_CONFIG };
|
||||
|
||||
Object.entries({ fail, success }).forEach(([key, value]) => {
|
||||
if (value !== undefined)
|
||||
config[key] = value;
|
||||
});
|
||||
Object.entries({ handler, combine }).forEach(([key, value]) => {
|
||||
if (value !== undefined)
|
||||
config[key] = { ...config[key], ...value };
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
function get_error_handler(config) {
|
||||
const result = _merge_error_config(config?.errorHandling);
|
||||
|
||||
if (!config?.form) {
|
||||
result.combine = { ...result.combine, form: [] };
|
||||
} else {
|
||||
const formHandler = result.handler.form;
|
||||
result.handler = { ...result.handler, form: errors => formHandler(config.form, errors) };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
function get_error_list(error) {
|
||||
if (error.response) {
|
||||
if (error.response.status == 404) {
|
||||
return [{
|
||||
type: 'not_found',
|
||||
message: error.message,
|
||||
url: error.request.responseURL
|
||||
}];
|
||||
} else {
|
||||
return error.response.data.errors;
|
||||
}
|
||||
} else if (error.request) {
|
||||
return [{
|
||||
type: 'site_failed',
|
||||
message: error.message,
|
||||
url: error.request.responseURL
|
||||
}];
|
||||
} else {
|
||||
return [{
|
||||
type: 'script',
|
||||
message: error.message
|
||||
}];
|
||||
}
|
||||
}
|
||||
function popHandleableErrors(errorHandling, errors) {
|
||||
const result = {};
|
||||
const copy = [];
|
||||
while (errors.length)
|
||||
copy.push(errors.pop());
|
||||
for (var error of copy) {
|
||||
let type = error.type;
|
||||
let newType = null;
|
||||
for (var t in errorHandling.combine) {
|
||||
let newTypeCombinesType = errorHandling
|
||||
.combine[t]
|
||||
.includes(type);
|
||||
let newTypeHasHandler = errorHandling.handler[t];
|
||||
if (newTypeCombinesType && newTypeHasHandler) {
|
||||
newType = t;
|
||||
if (newType == 'form')
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newType)
|
||||
type = newType;
|
||||
const handler = errorHandling.handler[type];
|
||||
if (handler) {
|
||||
if (!result[type])
|
||||
result[type] = [];
|
||||
if (Array.isArray(error))
|
||||
result[type].push(...error);
|
||||
else
|
||||
result[type].push(error);
|
||||
continue;
|
||||
}
|
||||
errors.push(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const fhcApiAxios = axios.create({
|
||||
timeout: 500000,
|
||||
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/"
|
||||
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
||||
+ FHC_JS_DATA_STORAGE_OBJECT.ci_router
|
||||
+ "/"
|
||||
});
|
||||
|
||||
fhcApiAxios.interceptors.request.use(config => {
|
||||
@@ -97,57 +425,51 @@ export default {
|
||||
return config;
|
||||
});
|
||||
|
||||
fhcApiAxios.interceptors.response.use(response => {
|
||||
if (response.config?.errorHandling == 'off'
|
||||
|| response.config?.errorHandling === false
|
||||
|| response.config?.errorHandling == 'fail')
|
||||
return _clean_return_value(response);
|
||||
|
||||
// NOTE(chris): loop through errors
|
||||
if (response.data.errors)
|
||||
response.data.errors = response.data.errors.filter(
|
||||
err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, response.config)
|
||||
);
|
||||
|
||||
return _clean_return_value(response);
|
||||
}, error => {
|
||||
if (error.code == 'ERR_CANCELED')
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
|
||||
if (error.config?.errorHandling == 'off'
|
||||
|| error.config?.errorHandling === false
|
||||
|| error.config?.errorHandling == 'success')
|
||||
return Promise.reject(error);
|
||||
|
||||
if (error.response) {
|
||||
if (error.response.status == 404) {
|
||||
app.config.globalProperties.$fhcAlert.alertDefault('error', error.message, error.request.responseURL, true);
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
}
|
||||
fhcApiAxios.interceptors.response.use(
|
||||
response => {
|
||||
if (response.config?.errorHandling == 'off'
|
||||
|| response.config?.errorHandling === false
|
||||
|| response.config?.errorHandling == 'fail')
|
||||
return clean_return_value(response);
|
||||
|
||||
// NOTE(chris): loop through errors
|
||||
error.response.data.errors = error.response.data.errors.filter(
|
||||
err => (error.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, error.config)
|
||||
);
|
||||
if (!error.response.data.errors.length)
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
} else if (error.request) {
|
||||
app.config.globalProperties.$fhcAlert.alertDefault('error', error.message, error.request.responseURL);
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
} else {
|
||||
app.config.globalProperties.$fhcAlert.alertError(error.message);
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
}
|
||||
if (response.data.errors)
|
||||
response.data.errors = response.data.errors.filter(
|
||||
err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, response.config)
|
||||
);
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
return clean_return_value(response);
|
||||
},
|
||||
error => {
|
||||
if (error.code == 'ERR_CANCELED')
|
||||
return Promise.reject({ handled: true, ...error });
|
||||
|
||||
const errorConfig = get_error_handler(error.config);
|
||||
|
||||
if (!errorConfig.fail)
|
||||
return Promise.reject(error);
|
||||
|
||||
const remaining = get_error_list(error);
|
||||
|
||||
const errors = popHandleableErrors(errorConfig, remaining);
|
||||
|
||||
for (var type in errors) {
|
||||
errorConfig.handler[type](errors[type]);
|
||||
}
|
||||
|
||||
if (remaining.length)
|
||||
return Promise.reject(error);
|
||||
|
||||
return Promise.reject({ handled: true, ...error });
|
||||
}
|
||||
);
|
||||
|
||||
app.config.globalProperties.$api = {
|
||||
getUri(url) {
|
||||
return fhcApiAxios.getUri({url});
|
||||
},
|
||||
get(form, uri, params, config) {
|
||||
[uri, params, config] = _get_config(form, uri, params, config);
|
||||
[uri, params, config] = get_config(form, uri, params, config);
|
||||
if (params) {
|
||||
if (config)
|
||||
config.params = params;
|
||||
@@ -157,11 +479,58 @@ export default {
|
||||
return fhcApiAxios.get(uri, config);
|
||||
},
|
||||
post(form, uri, data, config) {
|
||||
[uri, data, config] = _get_config(form, uri, data, config);
|
||||
[uri, data, config] = get_config(form, uri, data, config);
|
||||
return fhcApiAxios.post(uri, data, config);
|
||||
},
|
||||
call(factory, configoverwrite, form) {
|
||||
let {method, url, params, config} = factory;
|
||||
if (Array.isArray(factory)) {
|
||||
const $api = app.config.globalProperties.$api;
|
||||
|
||||
return Promise
|
||||
.allSettled(factory.map((config, index) => {
|
||||
if (!Array.isArray(config))
|
||||
config = ['#' + index, config];
|
||||
return $api.call(config[1], {
|
||||
errorHeader: config[0],
|
||||
errorHandling: false
|
||||
});
|
||||
}))
|
||||
.then(result => {
|
||||
const [ , , config ] = get_config(form, undefined, undefined, configoverwrite || {});
|
||||
const errorConfig = get_error_handler(config);
|
||||
|
||||
if (!errorConfig.success && !errorConfig.fail) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const typedErrors = {};
|
||||
for (var res of result) {
|
||||
const [ allowed, item ] = res.status === 'fulfilled'
|
||||
? [ errorConfig.success, res.value ]
|
||||
: [ errorConfig.fail, res.reason ];
|
||||
if (!allowed)
|
||||
return;
|
||||
|
||||
const errors = popHandleableErrors(errorConfig, get_error_list(item));
|
||||
|
||||
for (var type in errors) {
|
||||
if (!typedErrors[type])
|
||||
typedErrors[type] = {
|
||||
[item.config.errorHeader]: errors[type]
|
||||
};
|
||||
else
|
||||
typedErrors[type][item.config.errorHeader] = errors[type];
|
||||
}
|
||||
};
|
||||
|
||||
for (var errType in typedErrors) {
|
||||
errorConfig.handler[errType](typedErrors[errType]);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
let { method, url, params, config } = factory;
|
||||
if (configoverwrite !== undefined) {
|
||||
config = configoverwrite;
|
||||
}
|
||||
@@ -177,141 +546,6 @@ export default {
|
||||
} else {
|
||||
console.error("FhcApi: method not allowed:", method);
|
||||
}
|
||||
},
|
||||
_defaultErrorHandlers: {
|
||||
validation(error, config) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
if (config?.form) {
|
||||
config.form.clearValidation();
|
||||
config.form.setFeedback(false, error.messages);
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(error.messages)) {
|
||||
error.messages.forEach($fhcAlert.alertError);
|
||||
return false;
|
||||
} else if (typeof error.messages == 'object') {
|
||||
if (config?.errorHeader)
|
||||
Object.values(error.messages).forEach(
|
||||
value => $fhcAlert.alertDefault(
|
||||
'error',
|
||||
Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
|
||||
value,
|
||||
true
|
||||
)
|
||||
);
|
||||
else
|
||||
Object.entries(error.messages).forEach(
|
||||
([key, value]) => $fhcAlert.alertDefault('error', key, value, true)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
general(error, config) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
if (config?.form)
|
||||
config.form.setFeedback(false, error.message);
|
||||
else if (config?.errorHeader)
|
||||
$fhcAlert.alertDefault(
|
||||
'error',
|
||||
Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
|
||||
error.message,
|
||||
true
|
||||
);
|
||||
else
|
||||
$fhcAlert.alertError(error.message);
|
||||
},
|
||||
php(error) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
switch (error.severity) {
|
||||
case 'Warning':
|
||||
case 'Core Warning':
|
||||
case 'Compile Warning':
|
||||
case 'User Warning':
|
||||
$fhcAlert.alertDefault('warn', 'PHP ' + error.severity, message, true);
|
||||
break;
|
||||
case 'Notice':
|
||||
case 'User Notice':
|
||||
case 'Runtime Notice':
|
||||
$fhcAlert.alertDefault('info', 'PHP ' + error.severity, message, true);
|
||||
break;
|
||||
default:
|
||||
message = 'Type: PHP ' + error.severity + '\n\n' + message;
|
||||
$fhcAlert.alertSystemError(message);
|
||||
break;
|
||||
}
|
||||
},
|
||||
exception(error) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
message += 'Type: ' + error.class + '\n\n';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
$fhcAlert.alertSystemError(message);
|
||||
},
|
||||
db(error) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
if (error.heading !== undefined)
|
||||
message += error.heading + '\n\n';
|
||||
if (error.code !== undefined)
|
||||
message += 'Code: ' + error.code + '\n\n';
|
||||
if (error.sql !== undefined)
|
||||
message += 'SQL: ' + error.sql + '\n\n';
|
||||
if (error.message !== undefined)
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
else if (error.messages !== undefined)
|
||||
message += 'Messages: ' + error.messages.join('\n\t') + '\n\n';
|
||||
if (error.filename !== undefined)
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
if (error.line !== undefined)
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
|
||||
$fhcAlert.alertSystemError(message);
|
||||
},
|
||||
auth(error, config) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
message += 'Controller name: ' + error.controller + '\n';
|
||||
message += 'Method name: ' + error.method + '\n';
|
||||
message += 'Required permissions: ' + error.required_permissions;
|
||||
if (config?.errorHeader)
|
||||
$fhcAlert.alertDefault(
|
||||
'error',
|
||||
Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
|
||||
error.message,
|
||||
true
|
||||
);
|
||||
else
|
||||
$fhcAlert.alertDefault('error', error.message, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -140,7 +140,16 @@ const helperApp = Vue.createApp({
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<pv-toast ref="toast" class="fhc-alert" :base-z-index="99999"></pv-toast>
|
||||
<pv-toast ref="toast" class="fhc-alert" :base-z-index="99999">
|
||||
<template #message="{ message }">
|
||||
<!--span :class="slotProps.iconClass"></span-->
|
||||
<div class="p-toast-message-text">
|
||||
<span class="p-toast-summary">{{ message.summary }}</span>
|
||||
<div v-if="message.detail && message.html" class="p-toast-detail" v-html="message.detail"></div>
|
||||
<div v-else-if="message.detail" class="p-toast-detail">{{ message.detail }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</pv-toast>
|
||||
<pv-toast ref="alert" class="fhc-alert" :base-z-index="99999" position="center">
|
||||
<template #message="slotProps">
|
||||
<i class="fa fa-circle-exclamation fa-2xl mt-3"></i>
|
||||
@@ -263,18 +272,18 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
alertDefault(severity, title, message, sticky = false) {
|
||||
let options = { severity: severity, summary: title, detail: message};
|
||||
alertDefault(severity, title, message, sticky = false, html = false) {
|
||||
let options = { severity: severity, summary: title, detail: message, html };
|
||||
|
||||
if (!sticky)
|
||||
options.life = 3000;
|
||||
|
||||
helperAppInstance.$refs.toast.add(options);
|
||||
},
|
||||
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false){
|
||||
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false, html = false){
|
||||
// Check, if array has only string values
|
||||
if (messageArray.every(message => typeof message === 'string')) {
|
||||
messageArray.forEach(message => this.alertDefault(severity, title, message, sticky));
|
||||
messageArray.forEach(message => this.alertDefault(severity, title, message, sticky, html));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -146,12 +146,13 @@ export function tagHeaderFilter(headerValue, rowValue, rowData, filterParams)
|
||||
if (Array.isArray(data))
|
||||
{
|
||||
combinedText = data
|
||||
.filter(item => item?.done === false)
|
||||
.map(item => `${item?.beschreibung} ${item?.notiz}`)
|
||||
.join(' ');
|
||||
}
|
||||
else if (typeof data === 'object' && data !== null)
|
||||
{
|
||||
combinedText = `${data?.beschreibung} ${data?.notiz}`;
|
||||
combinedText = data?.erledigt === false ? `${data?.beschreibung} ${data?.notiz}` : '';
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user