mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-01 12:19:28 +00:00
Merge branch 'merge_FHC4_55354_55991_55992_60874_60876_60875_61229_61230_61231' into demo-cis40
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>`
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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>`
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user