Merge branch 'merge_FHC4_55354_55991_55992_60874_60876_60875_61229_61230_61231' into demo-cis40

This commit is contained in:
Harald Bamberger
2025-08-13 07:05:21 +02:00
18 changed files with 407 additions and 181 deletions
+4
View File
@@ -58,6 +58,10 @@ $config['tabs'] =
//if true, Anrechnungen can be added and edited in tab Anrechnungen
'editableAnrechnungen' => false,
],
'notes' => [
//if true, the count of Messages will be shown in the header of the Tab Messages
'showCountNotes' => true
]
];
// List of fields to show when ZGV_DOKTOR_ANZEIGEN is defined
@@ -62,10 +62,15 @@ class Config extends FHCAPI_Controller
'component' => './Stv/Studentenverwaltung/Details/Details.js',
'config' => $config['details']
];
$showSuffix = $config['notes']['showCountNotes'] ?? false;
$result['notes'] = [
'title' => $this->p->t('stv', 'tab_notes'),
'component' => './Stv/Studentenverwaltung/Details/Notizen.js'
'component' => './Stv/Studentenverwaltung/Details/Notizen.js',
'config' => $config['notes'],
'showSuffix' => $showSuffix && ($config['notes']['showCountNotes'] ?? false)
];
$result['contact'] = [
'title' => $this->p->t('stv', 'tab_contact'),
'component' => './Stv/Studentenverwaltung/Details/Kontakt.js',
@@ -35,8 +35,6 @@ class Favorites extends FHCAPI_Controller
// Load models
$this->load->model('system/Variable_model', 'VariableModel');
// TODO(chris): variable table might be to small to store favorites!
}
public function index()
@@ -62,6 +60,17 @@ class Favorites extends FHCAPI_Controller
$favorites = $this->input->post('favorites');
$removed = [];
while (strlen($favorites) > 64) {
$favObj = json_decode($favorites);
if (!$favObj->list)
break;
$removed[] = array_shift($favObj->list);
$favorites = json_encode($favObj);
}
if ($removed)
$this->addMeta('removed', $removed);
$result = $this->VariableModel->setVariable(getAuthUID(), 'stv_favorites', $favorites);
$this->getDataOrTerminateWithError($result);
@@ -24,7 +24,6 @@ class Status extends FHCAPI_Controller
'updateStatus' => ['admin:rw', 'assistenz:rw'],
'advanceStatus' => ['admin:rw', 'assistenz:rw'],
'confirmStatus' => ['admin:rw', 'assistenz:rw'],
]);
//Load Models
@@ -440,9 +439,10 @@ class Status extends FHCAPI_Controller
]);
if (!$this->form_validation->run())
{
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
$this->load->library('PrestudentLib');
$this->db->trans_start();
@@ -628,8 +628,9 @@ class Status extends FHCAPI_Controller
]);
if (!$this->form_validation->run())
{
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
// Start DB transaction
$this->db->trans_start();
@@ -106,6 +106,7 @@ class Student extends FHCAPI_Controller
$this->PrestudentModel->addSelect('p.staatsbuergerschaft');
$this->PrestudentModel->addSelect('p.matr_nr');
$this->PrestudentModel->addSelect('p.anrede');
$this->PrestudentModel->addSelect('p.zugangscode');
if (defined('ACTIVE_ADDONS') && strpos(ACTIVE_ADDONS, 'bewerbung') !== false) {
$this->PrestudentModel->addSelect(
+17
View File
@@ -21,6 +21,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller
'loadDokumente' => self::DEFAULT_PERMISSION_R,
'getMitarbeiter' => self::DEFAULT_PERMISSION_R,
'isBerechtigt' => self::DEFAULT_PERMISSION_R,
'getCountNotes' => self::DEFAULT_PERMISSION_R,
];
if(!is_array($permissions))
@@ -459,4 +460,20 @@ abstract class Notiz_Controller extends FHCAPI_Controller
return $this->terminateWithSuccess($result);
}
public function getCountNotes($person_id)
{
$this->NotizzuordnungModel->addSelect('COUNT(*) AS anzahl', false);
$result = $this->NotizzuordnungModel->loadWhere(
array('person_id' => $person_id)
);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$anzahl = current(getData($result));
return $this->terminateWithSuccess($anzahl->anzahl ?: 0);
}
}
+4
View File
@@ -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;
+6
View File
@@ -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
};
}
};
+2 -1
View File
@@ -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() {
+26 -7
View File
@@ -296,6 +296,7 @@ export default {
showId: false,
showLastupdate: false
},
newCount: null
}
},
methods: {
@@ -367,6 +368,7 @@ export default {
.catch(this.$fhcAlert.handleSystemError)
.finally(() => {
window.scrollTo(0, 0);
this.getCountNotes();
});
},
deleteNotiz(notiz_id) {
@@ -381,6 +383,7 @@ export default {
.catch(this.$fhcAlert.handleSystemError)
.finally(() => {
window.scrollTo(0, 0);
this.getCountNotes();
});
},
loadNotiz(notiz_id) {
@@ -424,6 +427,7 @@ export default {
.catch(this.$fhcAlert.handleSystemError)
.finally(() => {
window.scrollTo(0, 0);
this.getCountNotes();
});
},
reload() {
@@ -464,7 +468,6 @@ export default {
});
},
initTinyMCE() {
const vm = this;
tinymce.init({
target: this.$refs.editor.$refs.input, //Important: not selector: to enable multiple import of component
@@ -503,15 +506,30 @@ export default {
this.showVariables[columnToShow] = true;
});
},
getCountNotes(){
return this.$api
.call(this.endpoint.getCountNotes(this.id))
.then(
result => {
this.newCount = result.data;
this.$nextTick(() => {
this.$emit('updateCount', this.newCount);
});
})
.catch(this.$fhcAlert.handleSystemError);
}
},
created() {
this.initializeShowVariables();
this.getUid();
},
async mounted() {
if(this.showTinyMce){
this.initTinyMCE();
if (this.showTinyMce) {
await this.initTinyMCE();
}
this.$nextTick(() => {
this.getCountNotes();
});
},
watch: {
//watcher für Tinymce-Textfeld
@@ -548,16 +566,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"
@@ -12,26 +12,35 @@ export default {
},
data() {
return {
endpoint: ApiNotizPerson
endpoint: ApiNotizPerson,
countNotiz: ''
};
},
methods: {
updateCountNotes(countNew){
this.headerSuffix = "(" + countNew + ")";
this.$emit('update:suffix', this.headerSuffix);
}
},
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="classicFas"
type-id="person_id"
:id="modelValue.person_id"
show-document
show-tiny-mce
:visibleColumns="['titel','text','verfasser','bearbeiter','dokumente']"
@updateCount="updateCountNotes"
>
</core-notiz>
<!--
---------------------------------------------------------------------------------------------
-------------------- DESCRIPTION FOR PARAMETER PROPS ----------------------------------------
@@ -146,14 +146,15 @@ 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(() => {
this.$emit('reloadTable');
this.$reloadList();
});
},
changeStatusToAbbrecher(statusgrund_id) {
this
@@ -247,31 +248,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 +344,4 @@ export default {
</ul>
</div>
</div>`
};
};
@@ -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) {
@@ -238,7 +213,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 +226,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 +247,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>`
};
};
+45 -21
View File
@@ -14,7 +14,7 @@ export default {
emits: [
'update:modelValue',
'change',
'changed'
'changed',
],
props: {
config: {
@@ -40,7 +40,7 @@ export default {
currentTab() {
if (this.tabs[this.current])
return this.tabs[this.current];
return { component: 'div' };
},
value: {
@@ -97,13 +97,19 @@ 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
};
}
if (Array.isArray(config))
@@ -118,6 +124,11 @@ export default {
this.current = Object.keys(tabs)[0];
}
this.tabs = tabs;
},
updateSuffix(event) {
if (this.currentTab?.value) {
this.currentTab.value.suffix = event;
}
}
},
created() {
@@ -126,28 +137,34 @@ export default {
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 +174,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>`
};
+49 -9
View File
@@ -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);
}
});
+75 -1
View File
@@ -42,12 +42,15 @@ export default {
}
function _clean_return_value(response) {
if (typeof response.data === 'string' || response.data instanceof String)
return _clean_return_value({ data: 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;
@@ -161,6 +164,77 @@ export default {
return fhcApiAxios.post(uri, data, config);
},
call(factory, configoverwrite, form) {
if (Array.isArray(factory)) {
const $fhcAlert = app.config.globalProperties.$fhcAlert;
const $api = app.config.globalProperties.$api;
Promise
.allSettled(factory.map((config, index) => {
if (Array.isArray(config))
return $api.call(config[1], {
errorHeader: config[0],
errorHandling: false
});
else
return $api.call(config, {
errorHeader: '#' + index,
errorHandling: false
});
}))
.then(res => {
// TODO(chris): obey form & configoverwrite
let messagesError = [];
let messagesSuccessful = [];
res.forEach(result => {
if (result.status === 'fulfilled') {
//console.log(JSON.parse(result.value.data));
const successTitle = "<dt>" + result.value.data + "</dt>";
messagesSuccessful.push(successTitle + "ok");
} else {
const errorTitle = "<dt>" + result.reason.config.errorHeader + "</dt>";
const errorMsg = JSON.parse(result.reason.request.response);
const fullMessage = errorMsg.errors.map(error => {
if (error.type == 'validation') {
// TODO(chris): do we want the keys?
return '<dd>' + Object.values(error.messages).join("</dd><dd>") + '</dd>';
}
// TODO(chris): other types
if (error.message)
return '<dd>' + error.message + '</dd>';
if (error.messages)
return '<dd>' + error.messages.join("\n") + '</dd>';
// TODO(chris): what to do here
return '<dd>' + "Generic Error" + '</dd>'; // TODO(chris): translate
}).join("\n");
messagesError.push(errorTitle + fullMessage);
}
});
if (messagesError.length)
{
const test = document.createElement('b');
$fhcAlert.alertDefault(
'error',
messagesError.length + " Fehler", // TODO(chris): translate
'<dl>' + messagesError.join("") + '</dl>',
true,
true
);
}
if (messagesSuccessful.length)
{
const test = document.createElement('b');
$fhcAlert.alertDefault(
'info',
'Feedback',
messagesSuccessful.length + " erfolgreiche Statusänderung(en) durchgeführt", // TODO(chris): translate
false,
true
);
}
});
}
let {method, url, params, config} = factory;
if (configoverwrite !== undefined) {
config = configoverwrite;
+14 -5
View File
@@ -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;
+20
View File
@@ -37553,6 +37553,26 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'warn_removed_favs',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zu viele Favoriten! Die folgenden Einträge wurden entfernt: {items}',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Too many favorites! The following entries were removed: {items}',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'stv',