Merge branch 'master' into feature-30660/FHC4_StudierendenGUI_Prototyp

This commit is contained in:
cgfhtw
2023-11-29 15:40:24 +01:00
161 changed files with 18665 additions and 1139 deletions
+83
View File
@@ -0,0 +1,83 @@
.text-prewrap {
white-space: pre-line;
}
.accordion-button-primary {
background-color: #e7f1ff;
color: #0c63e4;
}
.accordion-button-primary:not(.collapsed) {
background-color: #cfe2ff;
color: #0a58ca;
}
.accordion-button-secondary {
background-color: #f0f1f2;
color: #616971;
}
.accordion-button-secondary:not(.collapsed) {
background-color: #e2e3e5;
color: #565e64;
}
.accordion-button-success {
background-color: #e8f3ee;
color: #177a4c;
}
.accordion-button-success:not(.collapsed) {
background-color: #d1e7dd;
color: #146c43;
}
.accordion-button-info {
background-color: #e7fafe;
color: #0cb6d8;
}
.accordion-button-info:not(.collapsed) {
background-color: #cff4fc;
color: #0aa2c0;
}
.accordion-button-warning {
background-color: #fff9e6;
color: #e6ae06;
}
.accordion-button-warning:not(.collapsed) {
background-color: #fff3cd;
color: #cc9a06;
}
.accordion-button-danger {
background-color: #fcebec;
color: #c6303e;
}
.accordion-button-danger:not(.collapsed) {
background-color: #f8d7da;
color: #b02a37;
}
.accordion-button-light {
background-color: #fefeff;
color: #dfe0e1;
}
.accordion-button-light:not(.collapsed) {
background-color: #fefefe;
color: #c6c7c8;
}
.accordion-button-dark {
background-color: #e9e9ea;
color: #1e2125;
}
.accordion-button-dark:not(.collapsed) {
background-color: #d3d3d4;
color: #1a1e21;
}
.tabulator-edit-list .tabulator-edit-list-item {
background-color: white;
}
.tabulator-edit-list .tabulator-edit-list-item:hover,
.tabulator-edit-list .tabulator-edit-list-item.active {
color: white;
}
+40
View File
@@ -0,0 +1,40 @@
@import '../../vendor/olifolkerd/tabulator5/dist/css/tabulator_bootstrap5.min.css';
/* Apply borders and background to Cell instead of the Row
* otherwise frozen columns won't look good (columns behind
* will be partial visible)
*/
.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;
}
+99
View File
@@ -0,0 +1,99 @@
:root {
--bs-body-font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
--bs-body-font-size: 14px;
--bs-body-line-height: 1.42857143;
--bs-body-color: #333;
}
html {
font-size: var(--bs-body-font-size);
}
h1, .h1 {
margin-top: 0;
font-size: 1.4rem;
}
h2, .h2 {
margin-top: 0;
font-size: 1.4rem;
}
h3, .h3 {
margin-top: 0;
font-size: 1.3rem;
}
h4, .h4 {
margin-top: 0;
font-size: 1.2rem;
}
h5, .h5 {
margin-top: 0;
font-size: 1.1rem;
}
h6, .h6 {
margin-top: 0;
font-size: 1rem;
}
.btn {
padding: 6px 12px;
}
.fhc-header {
display: flex;
justify-content: between;
flex: wrap;
align-items: center;
padding: 20px 15px 19px;
margin: 40px 0 20px;
border-bottom: solid 1px #eee;
}
@media (min-width:768px) { /* NOTE(chris): size "md" from bs5 */
.fhc-header {
flex-wrap: nowrap!important;
}
}
.fhc-header h1,
.fhc-header h2 {
font-size: 24px;
margin: 0;
}
.fhc-container {
padding: 0 15px;
max-width: 100%;
}
.tabulator {
font-size: var(--bs-body-font-size);
}
.fhc-container .tabulator {
border-left: 1px solid #dee2e6;
}
.fhc-container .tabulator .tabulator-row {
border-bottom: none;
}
.fhc-container .tabulator .tabulator-row .tabulator-frozen,
.fhc-container .tabulator .tabulator-row .tabulator-cell {
border-bottom: 1px solid #dee2e6;
}
.fhc-container .tabulator .tabulator-row.tabulator-row-even {
background-color: transparent;
}
.fhc-container .tabulator .tabulator-headers .tabulator-frozen,
.fhc-container .tabulator .tabulator-row.tabulator-row-odd .tabulator-frozen,
.fhc-container .tabulator .tabulator-row.tabulator-row-odd .tabulator-cell {
background-color: #fff;
}
.fhc-container .tabulator .tabulator-row.tabulator-row-even .tabulator-frozen,
.fhc-container .tabulator .tabulator-row.tabulator-row-even .tabulator-cell {
background-color: #f2f2f2;
}
.fhc-container .tabulator .tabulator-row.tabulator-selectable:hover .tabulator-frozen,
.fhc-container .tabulator .tabulator-row.tabulator-selectable:hover .tabulator-cell {
background-color: #ececec;
}
.fhc-container .tabulator .tabulator-col,
.fhc-container .tabulator .tabulator-cell {
border-right: 1px solid #dee2e6;
}
+5
View File
@@ -0,0 +1,5 @@
/* for removing horizontal scrollbar when form is embedded as i frame */
#uhstat1Container {
overflow-x:hidden;
height: 100%;
}
+26
View File
@@ -0,0 +1,26 @@
import StudierendenantragAntrag from "../../components/Studierendenantrag/Antrag.js";
import StudierendenantragStatus from "../../components/Studierendenantrag/Status.js";
import StudierendenantragInfoblock from "../../components/Studierendenantrag/Infoblock.js";
import VueDatePicker from "../../components/vueDatepicker.js.php";
import Phrasen from '../../plugin/Phrasen.js';
const app = Vue.createApp({
components: {
VueDatePicker,
StudierendenantragAntrag,
StudierendenantragStatus,
StudierendenantragInfoblock
},
data() {
return {
status: {
msg: '',
severity: ''
},
infoArray: []
};
}
});
app
.use(Phrasen)
.mount('#wrapper');
+12
View File
@@ -0,0 +1,12 @@
import StudierendenantragLeitung from '../../../components/Studierendenantrag/Leitung.js';
import Phrasen from '../../../plugin/Phrasen.js';
const app = Vue.createApp({
components: {
StudierendenantragLeitung
}
});
app
.use(Phrasen)
.use(primevue.config.default,{zIndex: {overlay: 9999}})
.mount('#wrapper');
@@ -0,0 +1,16 @@
import LvZuweisung from '../../../components/Studierendenantrag/Lvzuweisung.js';
import Phrasen from '../../../plugin/Phrasen.js';
const app = Vue.createApp({
components: {
LvZuweisung
},
computed: {
notinframe() {
return window.self === window.top;
}
}
});
app
.use(Phrasen)
.mount('#wrapper');
+11
View File
@@ -0,0 +1,11 @@
import LvPopup from '../../../components/Studierendenantrag/Leitung/LvPopup.js';
import Phrasen from '../../../plugin/Phrasen.js';
const app = Vue.createApp({
components: {
LvPopup
}
});
app
.use(Phrasen)
.mount('#wrapper');
+11
View File
@@ -0,0 +1,11 @@
/**
* javascript file for UHSTAT1 GUI
*/
$(document).ready(function ()
{
window.setTimeout(function() {
$("#uhstat_success_alert").fadeTo(500, 0).slideUp(500, function(){
$(this).remove();
});
}, 1000);
});
+44
View File
@@ -0,0 +1,44 @@
import BsModal from './Modal.js';
export default {
components: {
BsModal
},
mixins: [
BsModal
],
props: {
dialogClass: {
type: [String,Array,Object],
default: 'modal-dialog-centered'
},
/*
* NOTE(chris):
* Hack to expose in "emits" declared events to $props which we use
* in the v-bind directive to forward all events.
* @see: https://github.com/vuejs/core/issues/3432
*/
onHideBsModal: Function,
onHiddenBsModal: Function,
onHidePreventedBsModal: Function,
onShowBsModal: Function,
onShownBsModal: Function
},
data: () => ({
result: true
}),
mounted() {
this.modal = this.$refs.modalContainer.modal;
},
popup(msg, options) {
return BsModal.popup.bind(this)(msg, options);
},
template: `<bs-modal ref="modalContainer" class="bootstrap-alert" v-bind="$props">
<template v-slot:default>
<slot></slot>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
</template>
</bs-modal>`
}
+22
View File
@@ -0,0 +1,22 @@
import BsAlert from './Alert';
export default {
mixins: [
BsAlert
],
data: () => ({
result: false
}),
popup(msg, options) {
return BsAlert.popup.bind(this)(msg, options);
},
template: `<bs-modal ref="modalContainer" class="bootstrap-confirm" v-bind="$props">
<template v-slot:default>
<slot></slot>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" @click="result=true;this.hide()">OK</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</template>
</bs-modal>`
}
+37
View File
@@ -0,0 +1,37 @@
import BsAlert from './Alert';
export default {
mixins: [
BsAlert
],
props: {
placeholder: String,
default: String
},
data: () => ({
value: '',
result: false
}),
created() {
if (this.default)
this.value = this.default;
},
popup(msg, options) {
if (typeof options === 'string')
options = { default: options };
return BsAlert.popup.bind(this)(msg, options);
},
template: `<bs-modal ref="modalContainer" class="bootstrap-prompt" v-bind="$props">
<template v-slot:default>
<slot></slot>
<div>
<input ref="input" type="text" class="form-control" :placeholder="placeholder" v-model="value">
</div>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" @click="result=value;this.hide()">OK</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</template>
</bs-modal>`
}
+11 -6
View File
@@ -53,7 +53,9 @@ export const CoreFetchCmpt = {
*
*/
fetchData: function() {
this.loading = true; // loader started
this.loading = true; // loader started
this.error = false;
this.errorMessage = null;
// Checks if the apifunction is a callable function
if (typeof this.apiFunction == "function")
@@ -91,13 +93,16 @@ export const CoreFetchCmpt = {
*
*/
successHandler: function(response) {
this.$emit('dataFetched', response.data); // trigger the event dataFetched
this.$emit('dataFetched', response ? response.data : undefined); // trigger the event dataFetched
},
/**
*
*/
errorHandler: function(error) {
this.setError(error.message);
if (error.response.data.retval)
this.setError(error.response.data.retval);
else
this.setError(error.message);
},
/**
*
@@ -107,12 +112,12 @@ export const CoreFetchCmpt = {
}
},
template: `
<slot v-if="loading">
<slot v-if="loading" name="loading">
<div class="fetch-loader">Loading...</div>
</slot>
<slot v-if="error">
<slot v-else-if="error" name="error" :error-message="errorMessage">
<div class="fetch-error">{{ errorMessage }}</div>
</slot>
<slot v-else></slot>
`
};
+62
View File
@@ -0,0 +1,62 @@
import BsModal from './Bootstrap/Modal.js';
export default {
components: {
BsModal
},
props: {
timeout: {
type: Number,
default: 300
}
},
data() {
return {
t: null,
state: 0
}
},
methods: {
show() {
switch (this.state) {
case 0:
if (this.timeout) {
this.state = 1;
this.t = window.setTimeout(() => this.$refs.modal.show(), this.timeout);
return;
} else
return this.$refs.modal.show();
case 4:
return window.setTimeout(() => this.show(), 1);
}
},
hide() {
switch (this.state) {
case 1:
return window.clearTimeout(this.t);
case 2:
return window.setTimeout(() => this.hide(), 1);
case 3:
this.$refs.modal.hide();
}
}
},
mounted() {
this.$refs.modal.$refs.modal.addEventListener('show.bs.modal', () => {
this.state = 2;
});
this.$refs.modal.$refs.modal.addEventListener('shown.bs.modal', () => {
this.state = 3;
});
this.$refs.modal.$refs.modal.addEventListener('hide.bs.modal', () => {
this.state = 4;
});
this.$refs.modal.$refs.modal.addEventListener('hidden.bs.modal', () => {
this.state = 0;
});
},
template: `
<bs-modal ref="modal" class="fade text-center" dialog-class="modal-dialog-centered" backdrop="static" :keyboard="false">
Loading...
</bs-modal>`
}
@@ -0,0 +1,58 @@
import StudierendenantragAbmeldung from './Form/Abmeldung.js';
import StudierendenantragAbmeldungStgl from './Form/AbmeldungStgl.js';
import StudierendenantragUnterbrechung from './Form/Unterbrechung.js';
import StudierendenantragWiederholung from './Form/Wiederholung.js';
export default {
components: {
StudierendenantragAbmeldung,
StudierendenantragAbmeldungStgl,
StudierendenantragUnterbrechung,
StudierendenantragWiederholung
},
emits: [
'update:infoArray',
'update:statusMsg',
'update:statusSeverity'
],
props: {
antragType: String,
prestudentId: Number,
studierendenantragId: Number,
infoArray: Array,
statusMsg: String,
statusSeverity: String
},
data() {
return {
status: ''
};
},
computed: {
typeComponent() {
return 'Studierendenantrag' + this.antragType;
},
infoText() {
return this.$p.t('studierendenantrag/info_' + this.antragType + '_' + this.status);
}
},
template: `
<div class="studierendenantrag-antrag card">
<div class="card-header">
{{$p.t('studierendenantrag', 'title_' + antragType)}}
</div>
<div v-if="infoText && infoText.substr(0, 9) != '<< PHRASE'" class="alert alert-primary m-3" role="alert" v-html="infoText">
</div>
<component
:is="typeComponent"
class="card-body"
v-model:status="status"
:prestudent-id="prestudentId"
:studierendenantrag-id="studierendenantragId"
@setInfos="$emit('update:infoArray', $event)"
@setStatus="$emit('update:statusMsg', $event.msg);$emit('update:statusSeverity', $event.severity)"
>
</component>
</div>
`
}
@@ -0,0 +1,292 @@
import {CoreFetchCmpt} from '../../Fetch.js';
var _uuid = 0;
export default {
components: {
CoreFetchCmpt
},
emits: [
'setInfos',
'setStatus'
],
props: {
prestudentId: Number,
studierendenantragId: Number
},
data() {
return {
data: null,
saving: false,
errors: {
grund: [],
default: []
}
}
},
computed: {
statusSeverity() {
switch (this.data.status)
{
case 'Erstellt': return 'info';
case 'Genehmigt': return 'success';
default: return 'info';
}
},
loadUrl() {
if (this.studierendenantragId)
return '/components/Antrag/Abmeldung/getDetailsForAntrag/'+
this.studierendenantragId;
return '/components/Antrag/Abmeldung/getDetailsForNewAntrag/' +
this.prestudentId;
}
},
methods: {
load() {
return axios.get(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
this.loadUrl
).then(
result => {
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
return result;
}
);
},
createAntrag() {
bootstrap.Modal.getOrCreateInstance(this.$refs.modal).hide();
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_saving')})),
severity: 'warning'
});
this.saving = true;
for(var k in this.errors)
this.errors[k] = [];
axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Abmeldung/createAntrag/', {
studiensemester: this.data.studiensemester_kurzbz,
prestudent_id: this.data.prestudent_id,
grund: this.$refs.grund.value
}
).then(
result => {
if (result.data.error)
{
for (var k in result.data.retval)
{
if (this.errors[k] !== undefined)
this.errors[k].push(result.data.retval[k]);
else
this.errors.default.push(result.data.retval[k]);
}
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
severity: 'danger'
});
}
else
{
if (result.data.retval === true)
document.location += "";
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
else
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_open')})),
severity:'success'
});
}
this.saving = false;
}
);
},
cancelAntrag() {
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_cancelling')})),
severity: 'warning'
});
this.saving = true;
for(var k in this.errors)
this.errors[k] = [];
axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Abmeldung/cancelAntrag/', {
antrag_id: this.data.studierendenantrag_id
}
).then(
result => {
if (result.data.error)
{
for (var k in result.data.retval)
{
if (this.errors[k] !== undefined)
this.errors[k].push(result.data.retval[k]);
else
this.errors.default.push(result.data.retval[k]);
}
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
severity:'danger'
});
}
else
{
if (Number.isInteger(result.data.retval)) {
document.location = document.location.replace(/abmeldung\/([0-9]*)\/[0-9]*[\/]?$/, 'abmeldung/$1') + "/" + result.data.retval;
}
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
else
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_cancelled')})),
severity: 'danger'
});
}
this.saving = false;
}
);
}
},
created() {
this.uuid = _uuid++;
},
template: `
<div class="studierendenantrag-form-abmeldung">
<core-fetch-cmpt :api-function="load">
<div class="row">
<div class="col-12">
<div v-for="error in errors.default" class="alert alert-danger" role="alert" v-html="error">
</div>
<table class="table">
<tr>
<th>{{$p.t('lehre', 'studiengang')}}</th>
<td align="right">{{data.bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'organisationsform')}}</th>
<td align="right">{{data.orgform_bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
<td align="right">{{data.name}}</td>
</tr>
<tr>
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
<td align="right">{{data.matrikelnr}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'studienjahr')}}</th>
<td align="right">{{data.studienjahr_kurzbz}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'studiensemester')}}</th>
<td align="right">{{data.studiensemester_kurzbz}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'semester')}}</th>
<td align="right">{{data.semester}}</td>
</tr>
</table>
</div>
<div v-if="data.grund" class="mb-3">
<h5>{{$p.t('studierendenantrag', 'antrag_grund')}}:</h5>
<pre>{{data.grund}}</pre>
</div>
<div v-else class="col-sm-6 mb-3">
<label :for="'studierendenantrag-form-abmeldung-' + uuid + '-grund'" class="form-label">Grund:</label>
<textarea
class="form-control"
:class="{'is-invalid': errors.grund.length}"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-grund'"
rows="5"
:disabled="saving"
ref="grund"
required
></textarea>
<div v-if="errors.grund.length" class="invalid-feedback">
{{errors.grund.join(".")}}
</div>
</div>
<div class="col-12 text-end">
<button
v-if="data.studierendenantrag_id && data.canCancel"
type="button"
class="btn btn-danger"
@click="cancelAntrag"
:disabled="saving"
>
{{$p.t('studierendenantrag', 'btn_cancel')}}
</button>
<button
v-else-if="!data.studierendenantrag_id"
type="button"
class="btn btn-primary"
data-bs-toggle="modal"
:data-bs-target="'#studierendenantrag-form-abmeldung-' + uuid + '-modal'"
:disabled="saving"
>
{{$p.t('studierendenantrag', 'btn_create_Abmeldung')}}
</button>
<div
ref="modal"
class="modal fade text-start"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-modal'"
tabindex="-1"
:aria-labelledby="'studierendenantrag-form-abmeldung-' + uuid + '-modal-label'"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5
class="modal-title"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-modal-label'"
>
{{$p.t('studierendenantrag', 'title_Abmeldung')}}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$p.t('ui', 'schliessen')"></button>
</div>
<div class="modal-body" v-html="$p.t('studierendenantrag', 'warning_Abmeldung')">
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
@click="createAntrag">
{{$p.t('studierendenantrag', 'btn_create_Abmeldung')}}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<template v-slot:error="{errorMessage}">
<div class="alert alert-danger m-0" role="alert">
{{ errorMessage }}
</div>
</template>
</core-fetch-cmpt>
</div>
`
}
@@ -0,0 +1,263 @@
import {CoreFetchCmpt} from '../../Fetch.js';
var _uuid = 0;
export default {
components: {
CoreFetchCmpt
},
emits: [
'setInfos',
'setStatus'
],
props: {
prestudentId: Number,
studierendenantragId: Number
},
data() {
return {
data: null,
saving: false,
errors: {
grund: [],
default: []
}
}
},
computed: {
statusSeverity() {
switch (this.data?.status)
{
case 'Erstellt': return 'info';
case 'Genehmigt': return 'success';
default: return 'info';
}
},
loadUrl() {
if (this.studierendenantragId)
return '/components/Antrag/Abmeldung/getDetailsForAntrag/'+
this.studierendenantragId;
return '/components/Antrag/Abmeldung/getDetailsForNewAntrag/' +
this.prestudentId;
}
},
methods: {
load() {
return axios.get(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
this.loadUrl
).then(
result => {
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
return result;
}
);
},
createAntrag() {
bootstrap.Modal.getOrCreateInstance(this.$refs.modal).hide();
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_saving')})),
severity: 'warning'
});
this.saving = true;
for(var k in this.errors)
this.errors[k] = [];
axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Abmeldung/createAntrag/', {
studiensemester: this.data.studiensemester_kurzbz,
prestudent_id: this.data.prestudent_id,
grund: this.$refs.grund.value
}
).then(
result => {
if (result.data.error)
{
for (var k in result.data.retval)
{
if (this.errors[k] !== undefined)
this.errors[k].push(result.data.retval[k]);
else
this.errors.default.push(result.data.retval[k]);
}
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
severity: 'danger'
});
}
else
{
if (result.data.retval === true)
document.location += "";
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
else
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_open')})),
severity:'success'
});
}
this.saving = false;
}
);
},
appendDropDownText(event){
let templateText = this.$refs.grund;
if(event.target.value)
{
let templateT= this.$p.t('studierendenantrag', event.target.value);
templateText.value = templateT;
}
else
templateText.value = '';
},
},
created() {
this.uuid = _uuid++;
},
template: `
<div class="studierendenantrag-form-abmeldung">
<core-fetch-cmpt :api-function="load">
<div class="row">
<div class="col-12">
<table class="table">
<tr>
<th>{{$p.t('lehre', 'studiengang')}}</th>
<td align="right">{{data.bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'organisationsform')}}</th>
<td align="right">{{data.orgform_bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
<td align="right">{{data.name}}</td>
</tr>
<tr>
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
<td align="right">{{data.matrikelnr}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'studienjahr')}}</th>
<td align="right">{{data.studienjahr_kurzbz}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'studiensemester')}}</th>
<td align="right">{{data.studiensemester_kurzbz}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'semester')}}</th>
<td align="right">{{data.semester}}</td>
</tr>
</table>
</div>
<div v-if="data.grund" class="mb-3">
<h5>{{$p.t('studierendenantrag', 'antrag_grund')}}:</h5>
<pre class="text-prewrap">{{data?.grund}}</pre>
</div>
<div v-else class="col-sm-6 mb-3">
<label :for="'studierendenantrag-form-abmeldung-' + uuid + '-grund'" class="form-label">Grund:</label>
<div class="mb-2">
<select name="grundAv" @change="appendDropDownText
($event)">
<option value="" > --- bitte auswählen, sofern zutreffend ---- </option>
<option value="textLong_NichtantrittStudium">{{$p.t('studierendenantrag', 'dropdown_NichtantrittStudium')}}
</option>
<option value="textLong_ungenuegendeLeistung">{{$p.t('studierendenantrag', 'dropdown_ungenuegendeLeistung')}}
</option>
<option value="textLong_studentNichtAnwesend">{{$p.t('studierendenantrag', 'dropdown_nichtAnwesend')}}
</option>
<option value="textLong_PruefunstermineNichtEingehalten">{{$p.t('studierendenantrag', 'dropdown_PruefunstermineNichtEingehalten')}}
</option>
<option value="textLong_studentNichtGezahlt">{{$p.t('studierendenantrag', 'dropdown_nichtGezahlt')}}
</option>
<option value="textLong_plageat">{{$p.t('studierendenantrag', 'dropdown_plageat')}}
</option>
<option value="textLong_MissingZgv">{{$p.t('studierendenantrag', 'dropdown_MissingZgv')}}
</option>
</select>
</div>
<textarea
class="form-control"
:class="{'is-invalid': errors.grund.length}"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-grund'"
rows="5"
:disabled="saving"
ref="grund"
required
></textarea>
<div v-if="errors.grund.length" class="invalid-feedback">
{{errors.grund.join(".")}}
</div>
</div>
<div class="col-12 text-end">
<button
v-if="!data?.studierendenantrag_id"
type="button"
class="btn btn-primary"
data-bs-toggle="modal"
:data-bs-target="'#studierendenantrag-form-abmeldung-' + uuid + '-modal'"
:disabled="saving"
>
{{$p.t('studierendenantrag', 'btn_create_Abmeldung')}}
</button>
<div
ref="modal"
class="modal fade text-start"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-modal'"
tabindex="-1"
:aria-labelledby="'studierendenantrag-form-abmeldung-' + uuid + '-modal-label'"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5
class="modal-title"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-modal-label'"
>
{{$p.t('studierendenantrag', 'title_Abmeldung')}}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$p.t('ui', 'schliessen')"></button>
</div>
<div class="modal-body" v-html="$p.t('studierendenantrag', 'warning_AbmeldungStgl')">
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
@click="createAntrag">
{{$p.t('studierendenantrag', 'btn_create_Abmeldung')}}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<template v-slot:error="{errorMessage}">
<div class="alert alert-danger m-0" role="alert">
{{ errorMessage }}
</div>
</template>
</core-fetch-cmpt>
</div>
`
}
@@ -0,0 +1,361 @@
import {CoreFetchCmpt} from '../../Fetch.js';
import VueDatepicker from '../../vueDatepicker.js.php';
var _uuid = 0;
export default {
components: {
CoreFetchCmpt,
VueDatepicker
},
emits: [
'setInfos',
'setStatus'
],
props: {
prestudentId: Number,
studierendenantragId: Number
},
data() {
return {
data: null,
saving: false,
errors: {
grund: [],
studiensemester: [],
datum_wiedereinstieg: [],
default: []
},
stsem: null,
currentWiedereinstieg: '',
siteUrl: FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router
}
},
computed: {
statusSeverity() {
switch (this.data.status)
{
case 'Erstellt': return 'info';
case 'Genehmigt': return 'success';
case 'Zurueckgezogen': return 'danger';
default: return 'info';
}
},
loadUrl() {
if (this.studierendenantragId)
return '/components/Antrag/Unterbrechung/getDetailsForAntrag/'+
this.studierendenantragId;
return '/components/Antrag/Unterbrechung/getDetailsForNewAntrag/' +
this.prestudentId;
},
datumWsFormatted() {
let datumUnformatted = '';
if (this.data.datum_wiedereinstieg) {
datumUnformatted = this.data.datum_wiedereinstieg;
} else {
if (this.stsem !== null && this.data.studiensemester[this.stsem].wiedereinstieg)
datumUnformatted = this.data.studiensemester[this.stsem].wiedereinstieg;
}
if (datumUnformatted === '')
return datumUnformatted;
let datum = new Date(datumUnformatted);
return datum.toLocaleDateString();
}
},
methods: {
load() {
return axios.get(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
this.loadUrl
).then(
result => {
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
return result;
}
);
},
createAntrag() {
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_saving')})),
severity: 'warning'
});
this.saving = true;
for(var k in this.errors)
this.errors[k] = [];
var formData = new FormData();
var attachment = this.$refs.attachment;
formData.append("attachment", attachment.files[0]);
formData.append("studiensemester", this.stsem !== null && this.data.studiensemester[this.stsem].studiensemester_kurzbz);
formData.append("prestudent_id", this.data.prestudent_id);
formData.append("grund", this.$refs.grund.value);
formData.append("datum_wiedereinstieg", this.stsem !== null && this.currentWiedereinstieg);
axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Unterbrechung/createAntrag/',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(
result => {
if (result.data.error)
{
for (var k in result.data.retval)
{
if (this.errors[k] !== undefined)
this.errors[k].push(result.data.retval[k]);
else
this.errors.default.push(result.data.retval[k]);
}
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
severity: 'danger'
});
}
else
{
if (Number.isInteger(result.data.retval))
document.location += "/" + result.data.retval;
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
else
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_created')})),
severity: 'info'
});
}
this.saving = false;
}
);
},
cancelAntrag() {
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_cancelling')})),
severity: 'warning'
});
this.saving = true;
for(var k in this.errors)
this.errors[k] = [];
axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Unterbrechung/cancelAntrag/', {
antrag_id: this.data.studierendenantrag_id
}
).then(
result => {
if (result.data.error)
{
for (var k in result.data.retval)
{
if (this.errors[k] !== undefined)
this.errors[k].push(result.data.retval[k]);
else
this.errors.default.push(result.data.retval[k]);
}
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
severity: 'danger'
});
}
else
{
if (Number.isInteger(result.data.retval)) {
document.location = document.location.replace(/unterbrechung\/([0-9]*)\/[0-9]*[\/]?$/, 'unterbrechung/$1') + "/" + result.data.retval;
}
this.data = result.data.retval;
if (this.data.status) {
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
else
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_cancelled')})),
severity: 'danger'
});
}
this.saving = false;
}
);
}
},
created() {
this.uuid = _uuid++;
},
template: `
<div class="studierendenantrag-form-unterbrechung">
<core-fetch-cmpt :api-function="load">
<div class="row">
<div class="col-12">
<div v-for="error in errors.default" class="alert alert-danger" role="alert" v-html="error">
</div>
<table class="table">
<tr>
<th>{{$p.t('lehre', 'studiengang')}}</th>
<td align="right">{{data.bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'organisationsform')}}</th>
<td align="right">{{data.orgform_bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
<td align="right">{{data.name}}</td>
</tr>
<tr>
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
<td align="right">{{data.matrikelnr}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'studienjahr')}}</th>
<td align="right" v-if="data.studierendenantrag_id">{{data.studienjahr_kurzbz}}</td>
<td align="right" v-else>{{stsem === null ? '' : data.studiensemester[stsem].studienjahr_kurzbz}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'semester')}}</th>
<td align="right" v-if="data.studierendenantrag_id">{{data.semester}}</td>
<td align="right" v-else>{{stsem === null ? '' : data.studiensemester[stsem].semester}}</td>
</tr>
</table>
</div>
<div class="col-sm-6 mb-3">
<label :for="'studierendenantrag-form-abmeldung-' + uuid + '-stsem'" class="form-label">
{{$p.t('lehre', 'studiensemester')}}
</label>
<div v-if="data.studierendenantrag_id">
{{data.studiensemester_kurzbz}}
</div>
<div v-else>
<select
class="form-select"
:class="{'is-invalid': errors.studiensemester.length}"
v-model="stsem"
required
:id="'studierendenantrag-form-abmeldung-' + uuid + '-stsem'"
@input="currentWiedereinstieg = ''"
>
<option v-for="(stsem, index) in data.studiensemester" :key="index" :value="index">
{{stsem.studiensemester_kurzbz}}
</option>
</select>
<div v-if="errors.studiensemester.length" class="invalid-feedback">
{{errors.studiensemester.join(".")}}
</div>
</div>
</div>
<div class="col-sm-6 mb-3">
<label class="form-label">
{{$p.t('studierendenantrag', 'antrag_datum_wiedereinstieg')}}
</label>
<div v-if="data.studierendenantrag_id">
{{datumWsFormatted}}
</div>
<div v-else-if="stsem === null">
<select class="form-select" disabled>
<option selected>{{$p.t('ui/select_studiensemester')}}</option>
</select>
</div>
<div v-else>
<select v-model="currentWiedereinstieg" class="form-select">
<option v-for="sem in data.studiensemester[stsem].wiedereinstieg" :key="sem.studiensemester_kurzbz" :value="sem.start">
{{sem.studiensemester_kurzbz}}
</option>
</select>
</div>
<div v-if="errors.datum_wiedereinstieg.length" class="invalid-feedback d-block">
{{errors.datum_wiedereinstieg.join(".")}}
</div>
</div>
<div v-if="data.studierendenantrag_id" class="mb-3">
<h5>{{$p.t('studierendenantrag', 'antrag_grund')}}:</h5>
<textarea class="form-control" rows="5" readonly>{{data.grund}}</textarea>
</div>
<div v-else class="col-sm-6 mb-3">
<label :for="'studierendenantrag-form-abmeldung-' + uuid + '-grund'" class="form-label">Grund:</label>
<textarea
class="form-control"
:class="{'is-invalid': errors.grund.length}"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-grund'"
rows="5"
:disabled="saving"
ref="grund"
required
></textarea>
<div v-if="errors.grund.length" class="invalid-feedback">
{{errors.grund.join(".")}}
</div>
</div>
<div class="col-12 mb-3">
<div v-if="data.studierendenantrag_id">
<a v-if="data.dms_id" target="_blank" :href="siteUrl + '/lehre/Antrag/Attachment/Show/' + data.dms_id"> {{$p.t('studierendenantrag', 'antrag_dateianhaenge')}} </a>
<span v-else>{{$p.t('studierendenantrag', 'no_attachments')}}</span>
</div>
<div v-else>
<label
:for="'studierendenantrag-form-abmeldung-' + uuid + '-attachment'"
class="form-label">
{{$p.t('studierendenantrag', 'antrag_dateianhaenge')}}
</label>
<input
class="form-control"
type="file"
ref="attachment"
:id="'studierendenantrag-form-abmeldung-' + uuid + '-attachment'"
name="attachment">
</div>
</div>
<div class="col-12 text-end">
<button
v-if="!data.studierendenantrag_id"
type="button"
class="btn btn-primary"
@click="createAntrag"
:disabled="saving"
>
{{$p.t('studierendenantrag', 'btn_create_Unterbrechung')}}
</button>
<button
v-else-if="data.status == 'Erstellt'"
type="button"
class="btn btn-danger"
@click="cancelAntrag"
:disabled="saving"
>
{{$p.t('studierendenantrag', 'btn_cancel')}}
</button>
</div>
</div>
<template v-slot:error="{errorMessage}">
<div class="alert alert-danger m-0" role="alert">
{{ errorMessage }}
</div>
</template>
</core-fetch-cmpt>
</div>
`
}
@@ -0,0 +1,210 @@
import {CoreFetchCmpt} from '../../Fetch.js';
import VueDatepicker from '../../vueDatepicker.js.php';
var _uuid = 0;
export default {
components: {
CoreFetchCmpt,
VueDatepicker
},
emits: [
'setInfos',
'setStatus',
'update:status'
],
props: {
status: String,
prestudentId: Number,
studierendenantragId: Number
},
data() {
return {
data: null,
saving: false,
errors: {
grund: [],
default: []
},
siteUrl: FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router,
infos: []
}
},
computed: {
statusSeverity() {
switch (this.data.status)
{
case 'Erstellt': return 'info';
case 'Genehmigt': return 'success';
case 'Verzichtet': return 'danger';
default: return 'info';
}
},
loadUrl() {
return '/components/Antrag/Wiederholung/getDetailsForNewAntrag/' +
this.prestudentId;
},
datumPruefungFormatted() {
if(!this.data.pruefungsdatum)
return '';
let datum = new Date(this.data.pruefungsdatum);
return datum.toLocaleDateString();
}
},
methods: {
load() {
return axios.get(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
this.loadUrl
).then(
result => {
this.data = result.data.retval;
if (!this.data.status || this.data.status == 'ErsteAufforderungVersandt' || this.data.status == 'ZweiteAufforderungVersandt') {
this.data.status = 'Offen';
this.data.statustyp = this.$p.t('studierendenantrag', 'status_open');
}
this.$emit('update:status', this.data.status);
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
return result;
}
);
},
createAntrag() {
this.createAntragWithStatus(true);
},
cancelAntrag() {
this.createAntragWithStatus(false);
},
createAntragWithStatus(repeat) {
let func = repeat ? 'createAntrag' : 'cancelAntrag';
let nextState = repeat ? 'Erstellt' : 'Verzichtet';
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_saving')})),
severity: 'warning'
});
this.saving = true;
for(var k in this.errors)
this.errors[k] = [];
axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Wiederholung/' + func + '/',
{
prestudent_id: this.data.prestudent_id,
studiensemester: this.data.studiensemester_kurzbz
}
).then(
result => {
if (result.data.error)
{
for (var k in result.data.retval)
{
if (this.errors[k] !== undefined)
this.errors[k].push(result.data.retval[k]);
else
this.errors.default.push(result.data.retval[k]);
}
this.$emit('setStatus', {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
severity: 'danger'
});
}
else
{
if (result.data.retval === true)
document.location += "";
this.data = result.data.retval;
if (!this.data.status)
this.data.status = nextState;
this.$emit('update:status', this.data.status);
this.$emit("setStatus", {
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
severity: this.statusSeverity
});
}
this.saving = false;
}
);
}
},
created() {
this.uuid = _uuid++;
},
mounted() {
this.infos = [...Array(5).keys()].map(n => ({
body: Vue.computed(() => this.$p.t('studierendenantrag', 'infotext_Wiederholung_' + n))
}));
this.$emit('setInfos', this.infos);
},
template: `
<div class="studierendenantrag-form-wiederholung">
<core-fetch-cmpt :api-function="load">
<div class="row">
<div class="col-12">
<div v-for="error in errors.default" class="alert alert-danger" role="alert" v-html="error">
</div>
<table class="table">
<tr>
<th>{{$p.t('lehre', 'studiengang')}}</th>
<td align="right">{{data.bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('lehre', 'organisationsform')}}</th>
<td align="right">{{data.orgform_bezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
<td align="right">{{data.name}}</td>
</tr>
<tr>
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
<td align="right">{{data.matrikelnr}}</td>
</tr>
<tr>
<th>{{$p.t('studierendenantrag', 'antrag_Wiederholung_pruefung')}}</th>
<td align="right">{{data.lvbezeichnung}}</td>
</tr>
<tr>
<th>{{$p.t('studierendenantrag', 'antrag_Wiederholung_pruefung_date')}}</th>
<td align="right">{{datumPruefungFormatted}}</td>
</tr>
</table>
</div>
<div class="col-12 d-flex justify-content-end gap-2">
<button
v-if="!data.studierendenantrag_id || data.status == 'Offen'"
type="button"
class="btn btn-primary"
@click="createAntrag"
:disabled="saving"
>
{{$p.t('studierendenantrag/antrag_Wiederholung_button_yes')}}
</button>
<!-- <button
v-if="!data.studierendenantrag_id || data.status == 'Offen'"
type="button"
class="btn btn-danger"
@click="cancelAntrag"
:disabled="saving"
>
{{$p.t('studierendenantrag/antrag_Wiederholung_button_no')}}
</button>-->
</div>
</div>
<template v-slot:error="{errorMessage}">
<div class="alert alert-danger m-0" role="alert">
{{ errorMessage }}
</div>
</template>
</core-fetch-cmpt>
</div>
`
}
@@ -0,0 +1,19 @@
export default {
props: {
infos: Array,
},
template: `
<div class="studierendenantrag-infoblock">
<div v-for="(info, index) in infos" :key="index" class="alert d-flex align-items-center" :class="'alert-' + (info.severity || 'info')" role="alert">
<i class="fa fa-lg flex-shrink-0 me-3" :class="'fa-' + (info.icon || 'info-circle')" role="img" aria-label="Info:"></i>
<div v-if="info.title">
<h4 v-if="info.title" class="alert-heading">
{{info.title}}
</h4>
<p class="mb-0" v-html="info.body"></p>
</div>
<p v-else class="mb-0" v-html="info.body"></p>
</div>
</div>
`
}
@@ -0,0 +1,355 @@
import LeitungHeader from './Leitung/Header.js';
import LeitungActions from './Leitung/Actions.js';
import LeitungTable from './Leitung/Table.js';
import GrundPopup from './Leitung/GrundPopup.js';
import LvPopup from './Leitung/LvPopup.js';
import BsAlert from '../Bootstrap/Alert.js';
import FhcLoader from '../Loader.js';
export default {
components: {
LeitungHeader,
LeitungTable,
LeitungActions,
FhcLoader
},
props: {
stgL: Array,
stgA: Array
},
data() {
return {
filter: undefined,
selectedData: [],
columns: [],
stgs: []
}
},
computed: {
stgkzL() {
if (!this.stgL)
return [];
return this.stgL.map(stg => parseInt(stg));
},
stgkzA() {
if (!this.stgA)
return [];
return this.stgA.map(stg => parseInt(stg));
}
},
methods: {
loadFilter() {
axios.get(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/getActiveStgs'
).then(result => {
this.stgs = Object.values(result.data.retval).sort((a,b) => a.bezeichnung == b.bezeichnung ? (a.orgform == b.orgform ? 0 : (a.orgform > b.orgform ? 1 : -1)) : (a.bezeichnung > b.bezeichnung ? 1 : -1));
}).catch(error => {
console.error(error);
});
},
changeFilter(evt) {
this.filter = evt.target.value || undefined;
this.reload();
},
reload() {
this.$refs.table.reload(this.filter);
this.loadFilter();
},
download() {
this.$refs.table.download();
},
actionApprove(evt, oks) {
var antraege = evt || [...this.selectedData];
if (!oks) {
oks = [];
}
var currentAntrag = antraege.shift();
if (currentAntrag) {
if (currentAntrag.typ != 'Wiederholung')
{
oks.push(currentAntrag);
this.actionApprove(antraege, oks);
}
else
{
let countAntrage = 0;
LvPopup
.popup(this.$p.t('studierendenantrag','title_show_lvs', currentAntrag), {
antragId: currentAntrag.studierendenantrag_id,
footer: true,
dialogClass: 'modal-lg',
countRemaining : antraege.filter(antrag => antrag.typ == 'Wiederholung').length
})
.then(result => {
if (result[0]) {
oks.push(currentAntrag);
if (result[1])
while (antraege.length)
oks.push(antraege.pop());
} else if (result[1]) {
while (antraege.length) {
currentAntrag = antraege.pop();
if (currentAntrag.typ != 'Wiederholung')
oks.push(currentAntrag);
}
}
this.actionApprove(antraege, oks);
})
.catch(() => {});
}
} else {
this.$refs.loader.show();
axios
.all(
oks.map(
antrag => axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/approve' + antrag.typ,
{
studierendenantrag_id: antrag.studierendenantrag_id
}
)
)
)
.then(this.showValidation)
.catch(this.showError);
}
},
actionReject(evt, gruende) {
var antraege = evt || this.selectedData;
if (!gruende)
gruende = [];
var currentAntrag = antraege.pop();
if (currentAntrag) {
GrundPopup
.popup(this.$p.t('studierendenantrag', 'title_grund', {id: currentAntrag.studierendenantrag_id}), {
countRemaining: antraege.length
})
.then(result => {
currentAntrag.grund = result[0];
gruende.push(currentAntrag);
if (result[1])
{
while (antraege.length)
{
currentAntrag = antraege.pop();
currentAntrag.grund = result[0];
gruende.push(currentAntrag);
}
}
this.actionReject(antraege, gruende);
})
.catch(() => {});
} else {
this.$refs.loader.show();
axios
.all(
gruende.map(
antrag => axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/reject' + antrag.typ,
{
studierendenantrag_id: antrag.studierendenantrag_id,
grund: antrag.grund
}
)
)
)
.then(this.showValidation)
.catch(this.showError);
}
},
actionReopen(evt) {
var antraege = evt || this.selectedData;
this.$refs.loader.show();
axios
.all(
antraege.map(
antrag => axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/reopenAntrag/',
{
studierendenantrag_id: antrag.studierendenantrag_id
}
)
)
)
.then(this.showValidation)
.catch(this.showError);
},
actionObject(evt) {
var antraege = evt || this.selectedData;
this.$refs.loader.show();
axios
.all(
antraege.map(
antrag => axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/objectAntrag/',
{
studierendenantrag_id: antrag.studierendenantrag_id
}
)
)
)
.then(this.showValidation)
.catch(this.showError);
},
actionoObjectionDeny(evt, gruende) {
var antraege = evt || this.selectedData;
if (!gruende)
gruende = [];
var currentAntrag = antraege.pop();
if (currentAntrag) {
GrundPopup
.popup(this.$p.t('studierendenantrag', 'title_grund', {id: currentAntrag.studierendenantrag_id}), {
countRemaining : antraege.length,
optional: true
})
.then(result => {
currentAntrag.grund = result[0];
gruende.push(currentAntrag);
if (result[1]) {
while (antraege.length) {
currentAntrag = antraege.pop();
currentAntrag.grund = result[0];
gruende.push(currentAntrag);
}
}
this.actionoObjectionDeny(antraege, gruende);
})
.catch(() => {});
} else {
this.$refs.loader.show();
axios
.all(
gruende.map(
antrag => axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/objectionDeny/',
{
studierendenantrag_id: antrag.studierendenantrag_id,
grund: antrag.grund
}
)
)
)
.then(this.showValidation)
.catch(this.showError);
}
},
actionObjectionApprove(evt, gruende) {
var antraege = evt || this.selectedData;
this.$refs.loader.show();
axios
.all(
antraege.map(
antrag => axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/objectionApprove/',
{
studierendenantrag_id: antrag.studierendenantrag_id
}
)
)
)
.then(this.showValidation)
.catch(this.showError);
},
actionCancel(evt) {
var antraege = evt || this.selectedData;
this.$refs.loader.show();
axios
.all(
antraege.map(
antrag => axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Abmeldung/cancelAntrag/',
{
antrag_id: antrag.studierendenantrag_id
}
)
)
)
.then(this.showValidation)
.catch(this.showError);
},
showValidation(results) {
var errors = results.filter(res => res.data.error);
this.$refs.loader.hide();
if (errors.length) {
let errorMsg = errors.map(
error =>
'Antrag ' +
JSON.parse(error.config.data).studierendenantrag_id +
'\n' +
Object.values(error.data.retval).join('\n')
).join('\n');
BsAlert.popup(errorMsg, {dialogClass: 'alert alert-danger'});
}
this.reload();
},
showError(error) {
this.$refs.loader.hide();
let msg = error.response.data;
if (msg.replace(/^\s+/, '').substr(0, 9) == '<!DOCTYPE' || msg.replace(/^\s+/, '').substr(0, 4).toLowerCase() == '<div')
msg = error.message;
BsAlert.popup(msg, {dialogClass: 'alert alert-danger'});
}
},
created() {
this.loadFilter();
},
template: `
<div class="studierendenantrag-leitung fhc-table">
<leitung-header
:stgs="stgs"
@input="changeFilter"
>
</leitung-header>
<leitung-actions
:stg-a="stgkzA"
:stg-l="stgkzL"
:selectedData="selectedData"
:columns="columns"
@reload="reload"
@action:approve="actionApprove"
@action:reject="actionReject"
@action:reopen="actionReopen"
@download="download"
>
</leitung-actions>
<leitung-table
ref="table"
:stg-a="stgkzA"
:stg-l="stgkzL"
v-model:columnData="columns"
v-model:selectedData="selectedData"
@action:approve="actionApprove"
@action:reject="actionReject"
@action:reopen="actionReopen"
@action:object="actionObject"
@action:objectionDeny="actionoObjectionDeny"
@action:objectionApprove="actionObjectionApprove"
@action:cancel="actionCancel"
@reload="reload"
>
</leitung-table>
<fhc-loader ref="loader"></fhc-loader>
</div>
`
}
@@ -0,0 +1,85 @@
import ActionsNew from './Actions/New.js';
import ActionsColumns from './Actions/Columns.js';
export default {
components: {
ActionsNew,
ActionsColumns
},
props: {
selectedData: Array,
columns: Array,
stgL: Array,
stgA: Array
},
emits: [
'reload',
'download',
'action:approve',
'action:reject',
'action:reopen'
],
data() {
return {
currentStudent: ''
}
},
computed: {
selectedCanBeApproved() {
if (!this.selectedData.length)
return false;
if (!this.selectedData.every(val => this.stgL.includes(val.studiengang_kz)))
return false;
return this.selectedData.filter(row => {
return (row.typ == 'Wiederholung' && row.status == 'Lvszugewiesen') || (row.typ != 'Wiederholung' && (row.status == 'Erstellt' || row.status == 'ErstelltStgl'));
}).length == this.selectedData.length;
},
selectedCanBeRejected() {
if (!this.selectedData.length)
return false;
if (!this.selectedData.every(val => this.stgL.includes(val.studiengang_kz)))
return false;
return this.selectedData.filter(row => {
return (row.typ == 'Unterbrechung' && row.status == 'Erstellt');
}).length == this.selectedData.length;
},
selectedCanBeReopened() {
if (!this.selectedData.length)
return false;
if (!this.selectedData.every(val => this.stgA.includes(val.studiengang_kz)))
return false;
return this.selectedData.filter(row => {
return (row.typ == 'Wiederholung' && row.status == 'Verzichtet');
}).length == this.selectedData.length;
},
newUrl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + this.currentStudent;
}
},
methods: {
hideModal() {
bootstrap.Modal.getInstance(this.$refs.modal).hide();
}
},
template: `
<div class="studierendenantrag-leitung-actions fhc-table-actions d-flex flex-wrap justify-content-between mb-2">
<div class="d-flex align-items-center gap-2">
<actions-new @reload="$emit('reload')"></actions-new>
<button type="button" class="btn btn-outline-secondary" @click="$emit('reload')" :title="$p.t('table','reload')">
<i class="fa-solid fa-rotate-right"></i>
</button>
<span>{{$p.t('table', 'with_selected', {count: selectedData.length})}}</span>
<button v-if="stgL.length" :disabled="!selectedCanBeApproved" type="button" class="btn btn-outline-secondary" @click="$emit('action:approve')">{{$p.t('studierendenantrag', 'btn_approve')}}</button>
<button v-if="stgL.length" :disabled="!selectedCanBeRejected" type="button" class="btn btn-outline-secondary" @click="$emit('action:reject')">{{$p.t('studierendenantrag', 'btn_reject')}}</button>
<button v-if="stgA.length" :disabled="!selectedCanBeReopened" type="button" class="btn btn-outline-secondary" @click="$emit('action:reopen')">{{$p.t('studierendenantrag', 'btn_reopen')}}</button>
</div>
<div>
<button type="button" class="btn btn-link" data-bs-toggle="collapse" href="#columns" :title="$p.t('table','spaltenEinAusblenden')"><i class="fa fa-table-columns"></i></button>
<button type="button" class="btn btn-link" @click="$emit('download')" :title="$p.t('table','download')"><i class="fa fa-download"></i></button>
</div>
<div class="col-12">
<actions-columns id="columns" class="collapse" :columns="columns"></actions-columns>
</div>
</div>
`
}
@@ -0,0 +1,22 @@
export default {
props: {
columns: Array
},
methods: {
toggleColumn(col) {
col.visible = !col.visible;
col.original.toggle()
}
},
template: `
<div class="studierendenantrag-leitung-actions-columns">
<div class="card mt-3">
<div class="card-body d-flex flex-wrap gap-2 justify-content-center">
<a v-for="col in columns" class="btn" :class="col.visible ? 'btn-dark' : 'btn-outline-dark'" href="#" @click.prevent="toggleColumn(col)">
{{col.title}}
</a>
</div>
</div>
</div>
`
}
@@ -0,0 +1,109 @@
import BsAlert from '../../../Bootstrap/Alert.js';
import BsModal from '../../../Bootstrap/Modal.js';
export default {
components: {
BsModal,
AutoComplete: primevue.autocomplete
},
emits: [
'reload'
],
data() {
return {
data: [],
student: '',
abortController: null
}
},
computed: {
newUrl() {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/lehre/Studierendenantrag/abmeldungStgl/' + this.student.prestudent_id;
}
},
methods: {
openForm() {
bootstrap.Modal.getInstance(this.$refs.modal).hide();
BsModal.popup(Vue.h('iframe', {
src: this.newUrl,
class: 'position-absolute top-0 start-0 w-100 h-100'
}), {
dialogClass: 'modal-fullscreen'
}, this.$p.t('studierendenantrag', 'antrag_header')).then(() => {
this.$emit('reload');
this.student = '';
});
},
loadData(evt) {
if (this.abortController)
this.abortController.abort();
this.abortController = new AbortController();
axios.post(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Abmeldung/getStudiengaengeAssistenz/',
evt,
{
signal: this.abortController.signal
}
).then(
result => {
if (result.data.error) {
BsAlert.popup(result.data.retval, {dialogClass: 'alert alert-danger'});
} else {
this.data = result.data.retval;
}
return result;
}
).catch(() => {});
}
},
template: `
<div class="studierendenantrag-leitung-actions-new" v-if="data">
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#newAntragModal">
<i class="fa fa-plus"></i>
{{$p.t('studierendenantrag','btn_new')}}
</button>
<div ref="modal" class="modal fade" id="newAntragModal" tabindex="-1" aria-labelledby="newAntragModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="newAntragModalLabel">{{$p.t('studierendenantrag','title_new_Abmeldung')}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$p.t('ui','schliessen')"></button>
</div>
<div class="modal-body">
<label for="newAntragModalAutoComplete">{{$p.t('person','studentIn')}}</label>
<div>
<auto-complete
class="w-100"
v-model="student"
:suggestions="data"
optionLabel = "name"
@complete="loadData"
inputId="newAntragModalAutoComplete"
dropdown
dropdown-mode="current"
>
<template #option="slotProps">
<div :title="slotProps.option.prestudent_id">
{{slotProps.option.name}}
</div>
</template>
</auto-complete>
</div>
</div>
<div class="modal-footer">
<button
class="btn btn-primary"
:disabled="!this.student"
@click.prevent="openForm">
{{$p.t('studierendenantrag','btn_create')}}
</button>
</div>
</div>
</div>
</div>
</div>
`
}
@@ -0,0 +1,59 @@
import BsAlert from '../../Bootstrap/Alert.js';
export default {
mixins: [
BsAlert,
],
props: {
placeholder: String,
default: String,
countRemaining: Number,
optional: Boolean
},
data: () => ({
value: '',
result: false,
check: false,
isInvalid: false
}),
methods: {
submit() {
if (!this.value && !this.optional) {
this.isInvalid = true;
} else {
this.result = [this.value, this.check];
this.hide();
}
}
},
created() {
if (this.default)
this.value = this.default;
},
popup(msg, options) {
if (typeof options === 'string')
options = { default: options };
return BsAlert.popup.bind(this)(msg, options);
},
template: `<bs-modal ref="modalContainer" class="bootstrap-prompt" v-bind="$props">
<template v-slot:title>
<slot></slot>
</template>
<template v-slot:default>
<div>
<textarea ref="input" class="form-control" :class="{'is-invalid' : isInvalid}" v-model="value"></textarea>
<div v-if="isInvalid" class="invalid-feedback">
{{$p.t('kvp','new.error.required')}}
</div>
</div>
</template>
<template v-slot:footer>
<div v-if="countRemaining > 0" class="form-check flex-grow-1">
<input ref="check" type="checkbox" class="form-check-input" id="cbid" v-model="check">
<label class="form-check-label" for="cbid">{{$p.t('studierendenantrag','fuer_alle_uebernehmen')}}</label>
</div>
<button type="button" class="btn btn-primary" @click="submit">{{$p.t('ui','ok')}}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{$p.t('ui','cancel')}}</button>
</template>
</bs-modal>`
}
@@ -0,0 +1,21 @@
export default {
props: {
stgs: Array
},
emits: [
'input'
],
template: `
<div class="studierendenantrag-leitung-header fhc-table-header d-flex align-items-center mb-2 gap-2">
<h3 class="h5 col m-0">{{$p.t('studierendenantrag', 'studierendenantraege')}}</h3>
<div v-if="stgs.length > 1" class="col-auto">
<select ref="stg_select" class="form-select" @input="$emit('input', $event)">
<option value="">{{$p.t('global', 'alle')}}</option>
<option v-for="stg in stgs" :key="stg.studiengang_kz" :value="stg.studiengang_kz">
{{stg.bezeichnung}} ({{stg.orgform}})
</option>
</select>
</div>
</div>
`
}
@@ -0,0 +1,147 @@
import BsAlert from '../../Bootstrap/Alert.js';
import {CoreFetchCmpt} from "../../Fetch.js";
export default {
components: {
CoreFetchCmpt
},
mixins: [
BsAlert
],
props: {
footer: Boolean,
antragId: Number,
countRemaining: Number
},
data(){
return {
lvs: null,
refresh: true,
result: false,
check: false
};
},
computed: {
lvzugelassen() {
let zwischen = {};
for (let k in this.lvs){
zwischen[k] = this.lvs[k] ? this.lvs[k].filter(lv=>lv.antrag_zugelassen) : null;
}
return zwischen;
},
lvzugelassenLength() {
return Object.values(this.lvzugelassen).reduce((result, current) => result + (current ? current.length : 0), 0);
}
},
methods: {
setlvs(param) {
if(param.error)
{
this.$refs.fetchCompt.error = true;
this.$refs.fetchCompt.errorMessage = param.retval;
}
else
this.lvs = param.retval;
},
loadlvs() {
if (!this.antragId)
return new Promise(() => {});
return axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Antrag/Wiederholung/getLvs/' + this.antragId);
},
submit(result) {
this.result = [result, this.check];
this.hide();
}
},
watch: {
antragId() {
Vue.nextTick(() => {
this.refresh = !this.refresh;
});
}
},
popup(msg, options) {
if (typeof options === 'string')
options = { default: options };
return BsAlert.popup.bind(this)(msg, options);
},
template: `<bs-modal ref="modalContainer" class="bootstrap-prompt" v-bind="$props" dialog-class="modal-lg">
<template v-slot:title>
<slot></slot>
</template>
<template v-slot:default>
<core-fetch-cmpt
ref="fetchCompt"
:refresh="refresh"
:api-function="loadlvs"
@data-fetched="setlvs">
<template #default>
<div v-if="lvzugelassenLength == 0">
{{$p.t('studierendenantrag','error_no_lvs')}}
</div>
<table v-else class="table caption-top" v-for="(lv_arr, sem) in lvzugelassen" :key="sem">
<caption>
<span class="d-flex justify-content-between">
<span>{{ $p.t('studierendenantrag',['title_lv_nicht_zugelassen', 'title_lv_wiederholen'][sem.substr(0,1)-1]) }}</span>
<span>{{sem.substr(1)}}</span>
</span>
</caption>
<thead v-if="lv_arr !== null">
<tr>
<th scope="col">{{$p.t('ui','bezeichnung')}}</th>
<th scope="col">{{$p.t('lehre','lehrform')}}</th>
<th scope="col">ECTS</th>
<th scope="col">{{$p.t('lehre','note')}}</th>
<th scope="col">
{{$p.t('global','anmerkung')}}
</th>
</tr>
</thead>
<tbody>
<tr v-if="lv_arr === null" class="table-warning">
<td colspan="5">{{$p.t('studierendenantrag/error_stg_last_semester')}}</td>
</tr>
<template v-else>
<tr v-for="lv in lv_arr">
<td>
<label class="w-100 m-1" :for="'flexSwitchCheckLv_' + lv.lehrveranstaltung_id">
{{lv.bezeichnung}}
</label>
</td>
<td>
<div class="m-1">
{{lv.lehrform_kurzbz}}
</div>
</td>
<td>
<div class="m-1">
{{lv.ects}}
</div>
</td>
<td>
<div class="m-1">
{{lv.note || '---'}}
</div>
</td>
<td>
<div class="m-1">
{{lv.antrag_anmerkung}}
</div>
</td>
</tr>
</template>
</tbody>
</table>
</template>
</core-fetch-cmpt>
</template>
<template v-if="footer" v-slot:footer>
<div v-if="countRemaining > 0" class="form-check flex-grow-1">
<input ref="check" type="checkbox" class="form-check-input" id="cbid" v-model="check">
<label class="form-check-label" for="cbid">{{$p.t('studierendenantrag','fuer_x_uebernehmen', {count: countRemaining})}}</label>
</div>
<button type="button" class="btn btn-primary" @click="submit(true)">{{$p.t('studierendenantrag','btn_approve')}}</button>
<button v-if="countRemaining > 0" type="button" class="btn btn-secondary" @click="submit(false)">{{$p.t('ui','skip')}}</button>
</template>
</bs-modal>`
}
@@ -0,0 +1,393 @@
import BsModal from '../../Bootstrap/Modal.js';
import {CoreFetchCmpt} from '../../Fetch.js';
import LvPopup from './LvPopup.js';
import { dateFilter } from '../../../tabulator/filters/Dates.js';
export default {
components: {
BsModal,
CoreFetchCmpt,
LvPopup
},
props: {
selectedData: Array,
columnData: Array,
stgL: Array,
stgA: Array
},
emits: [
'update:columnData',
'update:selectedData',
'action:approve',
'action:reject',
'action:reopen',
'action:object',
'action:objectionDeny',
'action:objectionApprove',
'action:cancel'
],
data() {
return {
ajaxUrl: FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/getAntraege/',
table: null,
lastHistoryClickedId: null,
historyData: [],
lvsData: null
}
},
methods: {
reload(stg) {
this.table.replaceData(this.ajaxUrl + (stg || ''));
},
download() {
this.table.download("csv", "data.csv", {
delimiter: ';',
bom: true
});
},
getHistory() {
if (this.lastHistoryClickedId === null)
return null;
return axios.get(
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/components/Antrag/Leitung/getHistory/' +
this.lastHistoryClickedId
).then(res => {
this.historyData = res.data.retval.sort((a, b) => a.insertamum > b.insertamum);
});
},
showHistoryGrund(grund) {
this.$refs.modalGrund.$el.addEventListener(
'hidden.bs.modal',
this.$refs.history.show,
{
once: true
}
);
this.$refs.modalGrundPre.innerHTML = grund;
},
showLVs(data) {
this.lvsData = data;
this.$refs.lvList.show();
}
},
async mounted() {
await this.$p.loadCategory(['lehre', 'studierendenantrag', 'person', 'global', 'ui']);
function dateFormatter(cell)
{
let val = cell.getValue();
if (!val)
return '';
let date = new Date(val);
return date.toLocaleDateString();
}
this.table = new Tabulator(this.$refs.table, {
placeholder:"Keine zu bearbeitenden Datensätze",
movableColumns: true,
maxHeight: '50vh',
layout: "fitDataFill",
ajaxURL: this.ajaxUrl,
persistence: { // NOTE(chris): do not store column titles
sort: true, //persist column sorting
filter: true, //persist filters
headerFilter: true, //persist header filters
group: true, //persist row grouping
page: true, //persist page
columns: ["width", "visible"], //persist columns
},
persistenceID: 'studierendenantrag_leitung_2023-11-14',
columns: [{
formatter: 'rowSelection',
titleFormatter: 'rowSelection',
titleFormatterParams: {
rowRange: 'active'
},
hozAlign: 'center',
headerSort: false
}, {
field: 'studierendenantrag_id',
title: '#'
}, {
field: 'bezeichnung',
title: this.$p.t('lehre', 'studiengang'),
headerFilter: 'list',
headerFilterParams: {
valuesLookup: true,
clearable: true,
autocomplete: true,
}
}, {
field: 'orgform',
title: this.$p.t('lehre', 'organisationsform'),
headerFilter: 'list',
headerFilterParams: {
valuesLookup: true,
clearable: true,
autocomplete: true,
}
}, {
field: 'typ',
title: this.$p.t('studierendenantrag', 'antrag_typ'),
headerFilter: 'list',
headerFilterParams: {
valuesLookup: true,
clearable: true,
autocomplete: true,
},
formatter: (cell, formatterParams, onRendered) => {
return this.$p.t('studierendenantrag','antrag_typ_' + cell.getValue());
}
}, {
field: 'statustyp',
title: this.$p.t('studierendenantrag', 'antrag_status'),
headerFilter: 'list',
headerFilterParams: {
valuesLookup: true,
clearable: true,
autocomplete: true,
},
formatter: (cell, formatterParams, onRendered) => {
let link = document.createElement('a');
link.href = "#";
link.innerHTML = cell.getValue();
link.addEventListener('click', e => {
e.preventDefault();
this.lastHistoryClickedId = cell.getData().studierendenantrag_id;
this.$refs.historyLoader.fetchData();
this.$refs.history.show();
});
return link;
}
}, {
field: 'matrikelnr',
title: this.$p.t('person', 'personenkennzeichen'),
headerFilter: 'input'
}, {
field: 'prestudent_id',
title: this.$p.t('lehre', 'prestudent'),
headerFilter: 'input'
}, {
field: 'name',
title: this.$p.t('global', 'name'),
mutator: (value, data) => (data.vorname + ' ' + data.nachname).replace(/^\s*(.*)\s*$/, '$1'),
headerFilter: 'input'
}, {
field: 'datum',
title: this.$p.t('global', 'datum'),
formatter: dateFormatter,
headerFilterFunc: 'dates',
headerFilter: dateFilter
}, {
field: 'datum_wiedereinstieg',
title: this.$p.t('studierendenantrag', 'antrag_datum_wiedereinstieg'),
formatter: dateFormatter,
headerFilterFunc: 'dates',
headerFilter: dateFilter
}, {
field: 'grund',
title: this.$p.t('studierendenantrag', 'antrag_grund'),
formatter: (cell, formatterParams, onRendered) => {
let link = document.createElement('a'),
val = cell.getValue();
link.href = "#modal-grund";
link.setAttribute('data-bs-toggle', 'modal');
link.innerHTML = this.$p.t('studierendenantrag', 'antrag_grund');
link.addEventListener('click', () => {
this.$refs.modalGrundPre.innerHTML = val;
});
return val ? link : '';
}
}, {
field: 'dms_id',
title: this.$p.t('studierendenantrag', 'antrag_dateianhaenge'),
formatter: (cell, formatterParams, onRendered) => {
let val = cell.getValue();
if (!val)
return '';
let link = document.createElement('a');
link.href = FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/lehre/Antrag/Attachment/show/' +
val;
link.setAttribute('target', '_blank');
link.innerHTML = '<i class="fa fa-paperclip" aria-hidden="true"></i>';
link.append(this.$p.t('studierendenantrag/antrag_anhang'));
return link;
}
}, {
field: 'actions',
frozen: true,
title: this.$p.t('ui', 'aktion'),
headerFilter: false,
headerSort: false,
formatter: (cell, formatterParams, onRendered) => {
let container = document.createElement('div'),
data = cell.getData();
container.className = "d-flex gap-2";
if ((data.typ == 'Abmeldung' || data.typ == 'AbmeldungStgl' || data.typ == 'Unterbrechung') && (data.status == 'Genehmigt' || data.status == 'Beeinsprucht' || data.status == 'EinspruchAbgelehnt' || data.status == 'EmailVersandt')) {
// NOTE(chris): Download PDF
let button = document.createElement('a');
// NOTE(chris): phrasen in attribues don't work if they are not preloaded
// it work in this case because the category has already been loaded before
button.innerHTML = '<i class="fa-solid fa-download" title="' + this.$p.t('studierendenantrag', 'btn_download_antrag') + '"></i>';
button.className = "btn btn-outline-secondary";
button.target = "_blank";
button.href = FHC_JS_DATA_STORAGE_OBJECT.app_root +
'content/pdfExport.php?xml=Antrag' + data.typ + '.xml.php&xsl=Antrag' + data.typ + '&id=' + data.studierendenantrag_id + '&output=pdf';
container.append(button);
}
if (data.typ == 'AbmeldungStgl' && data.status == 'Genehmigt') {
// NOTE(chris): Object
let button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_object'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:object', [cell.getData()]));
container.append(button);
}
if (data.typ == 'AbmeldungStgl' && data.status == 'Beeinsprucht') {
// NOTE(chris): Deny Objection
let button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_objection_deny'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:objectionDeny', [cell.getData()]));
container.append(button);
// NOTE(chris): Approve Objection
button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_objection_approve'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:objectionApprove', [cell.getData()]));
container.append(button);
}
if (this.stgA.includes(data.studiengang_kz)) {
// NOTE(chris): Reopen
if (data.typ == 'Wiederholung' && data.status == 'Verzichtet') {
let button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_reopen'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:reopen', [cell.getData()]));
container.append(button);
}
// NOTE(chris): Lv Zuweisen
if (data.typ == 'Wiederholung' && (data.status == 'Erstellt' || data.status == 'Lvszugewiesen')) {
let button = document.createElement('a');
button.append(this.$p.t('studierendenantrag', 'btn_lvzuweisen'));
button.className = "btn btn-outline-secondary";
button.href = FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
'/lehre/Antrag/Wiederholung/assistenz/' +
cell.getData().studierendenantrag_id + '/frame';
button.onclick = e => {
e.preventDefault();
BsModal.popup(Vue.h('iframe', {
src: button.href,
class: 'position-absolute top-0 start-0 w-100 h-100'
}), {
dialogClass: 'modal-fullscreen'
}, this.$p.t('studierendenantrag', 'title_lvzuweisen', cell.getData())).then(() => {
this.$emit('reload');
});
};
container.append(button);
}
// NOTE(chris): Cancel
if (data.typ == 'AbmeldungStgl' && (data.status == 'Erstellt' || data.status == 'Genehmigt' )) {
let button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_cancel'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click',() => this.$emit('action:cancel', [cell.getData()]));
container.append(button);
}
}
if (this.stgL.includes(data.studiengang_kz)) {
// NOTE(chris): Approve
if ((data.typ == 'Wiederholung' && data.status == 'Lvszugewiesen') || (data.typ != 'Wiederholung' && data.status == 'Erstellt')) {
let button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_approve'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:approve', [cell.getData()]));
container.append(button);
}
// NOTE(chris): Reject (Unterbrechung braucht grund)
if (data.status == 'Erstellt' && data.typ == 'Unterbrechung') {
let button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_reject'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:reject', [cell.getData()]));
container.append(button);
}
}
// NOTE(chris): Show LVs
if (data.typ == 'Wiederholung' && (data.status == 'Lvszugewiesen' || data.status == 'Genehmigt')) {
let button = document.createElement('button');
button.append(this.$p.t('studierendenantrag', 'btn_show_lvs'));
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.showLVs(cell.getData()));
container.append(button);
}
return container;
}
}]
});
this.table.on("tableBuilt", () => {
let columns = this.table.getColumns();
let columnData = [];
for (let col of columns) {
let def = col.getDefinition();
if (def.title && !def.frozen) {
columnData.push({
title: def.title,
visible: col.isVisible(),
original: col
});
}
}
this.$emit('update:columnData', columnData);
});
this.table.on("rowSelectionChanged", data => {
this.$emit('update:selectedData', data);
});
},
template: `
<div class="studierendenantrag-leitung-table">
<div ref="table"></div>
<bs-modal ref="modalGrund" id="modal-grund" class="fade">
<template #title>{{$p.t('studierendenantrag', 'antrag_grund')}}</template>
<textarea class="form-control" ref="modalGrundPre" style="width: 100%; height: 250px;" readonly></textarea>
</bs-modal>
<bs-modal ref="history" class="fade">
<template #title>{{$p.t('studierendenantrag', 'title_history', {id: lastHistoryClickedId})}}</template>
<core-fetch-cmpt ref="historyLoader" :api-function="getHistory">
<table v-if="historyData.length" class="table">
<tr v-for="status in historyData" :key="status.studierendenantrag_status_id">
<td>{{(new Date(status.insertamum)).toLocaleString()}}</td>
<td>{{status.insertvon}}</td>
<td>{{status.typ}}</td>
<td>
<a v-if="status.grund" href="#modal-grund" data-bs-toggle="modal" @click="showHistoryGrund(status.grund)">
{{$p.t('studierendenantrag', 'antrag_grund')}}
</a>
</td>
</tr>
</table>
</core-fetch-cmpt>
</bs-modal>
<lv-popup ref="lvList" class="fade" :antrag-id="lvsData ? lvsData.studierendenantrag_id : null">
{{$p.t('studierendenantrag', 'title_show_lvs', lvsData ? lvsData : {name: ''}) }}
</lv-popup>
</div>
`
}
@@ -0,0 +1,255 @@
import StudierendenantragStatus from './Status.js';
export default {
components: {
StudierendenantragStatus
},
props: {
antragId: Number,
initialStatusCode: String,
initialStatusMsg: String,
disabled: Boolean
},
data() {
return {
lvs: [],
isloading: false,
statusCode: '',
statusMsg: ''
};
},
computed: {
lvs1() {
return this.lvs[Object.keys(this.lvs).filter(key => key.substr(0, 1) == 1)] || [];
},
lvs2() {
return this.lvs[Object.keys(this.lvs).filter(key => key.substr(0, 1) == 2)] || [];
},
lvs1sem(){
return (Object.keys(this.lvs).filter(key => key.substr(0, 1) == 1).pop() || "1").substr(1);
},
lvs2sem(){
return (Object.keys(this.lvs).filter(key => key.substr(0, 1) == 2).pop() || "2").substr(1);
},
statusSeverity() {
switch (this.statusCode) {
case 0: return 'danger';
default: return 'info';
}
}
},
methods: {
save() {
this.isloading = true;
const forbiddenLvs = this.lvs1.filter(lv => lv.antrag_zugelassen && !lv._children).map(lv => ({
studierendenantrag_id: this.antragId,
lehrveranstaltung_id: lv.lehrveranstaltung_id,
zugelassen: 0,
anmerkung: lv.antrag_anmerkung || "",
studiensemester_kurzbz: this.lvs1sem
}));
const mandatoryLvs = this.lvs2.filter(lv => !lv._children).map(lv => ({
studierendenantrag_id: this.antragId,
lehrveranstaltung_id: lv.lehrveranstaltung_id,
zugelassen:lv.antrag_zugelassen ? 1 : 2,
anmerkung: lv.antrag_anmerkung || "",
studiensemester_kurzbz: this.lvs2sem
}));
axios.post(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Antrag/Wiederholung/saveLvs/', {forbiddenLvs, mandatoryLvs})
.then(response => {
if(!response.data.error) {
this.addAlert('Speichern erfolgreich', 'alert-success');
this.statusCode = response.data.retval[0].studierendenantrag_statustyp_kurzbz;
this.statusMsg = response.data.retval[0].typ;
} else {
this.addAlert(response.data.retval.message || response.data.retval, 'alert-danger');
this.statusCode = 0;
this.statusMsg = 'Error';
}
this.isloading = false;
}).catch(error => {
this.addAlert(error.message, 'alert-danger');
this.statusCode = 0;
this.statusMsg = 'Error';
this.isloading = false;
}).finally(() => {
window.scrollTo(0, 0);
});
},
addAlert(text, type) {
const para = document.createElement("p");
para.innerText = text;
para.className = "alert " + type + " alert-dismissible fade show";
const btn = document.createElement("button");
btn.className = "btn-close";
btn.type = "button";
btn.setAttribute("aria-label", "Close");
btn.setAttribute("data-bs-dismiss", "alert");
para.appendChild(btn);
this.$refs.alertbox.appendChild(para);
}
},
created() {
this.statusCode = this.initialStatusCode;
this.statusMsg = this.initialStatusMsg;
},
mounted() {
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Antrag/Wiederholung/getLvs/' + this.antragId).then(
result => {
if(result.data.error)
{
this.addAlert(result.data.retval, 'alert-danger');
this.isloading = true;
}
else
{
let res = {};
this.$p
.loadCategory(['ui', 'lehre', 'studierendenantrag', 'global'])
.then(() => {
for (var k in result.data.retval) {
if (result.data.retval[k] === null) {
const alert = document.createElement('div');
alert.innerHTML = this.$p.t('studierendenantrag', 'error_stg_last_semester');
alert.className = 'alert alert-warning';
alert.role = 'alert';
this.$refs["lvtable" + k.substr(0,1)].append(alert);
continue;
}
let lvs = result.data.retval[k].reduce((obj,lv) => {
obj[lv.studienplan_lehrveranstaltung_id] = lv;
return obj;
}, {});
for (var lv of Object.values(lvs)) {
if (!lv.studienplan_lehrveranstaltung_id_parent)
continue;
if (!lvs[lv.studienplan_lehrveranstaltung_id_parent])
console.error('parent not available');
else {
if (!lvs[lv.studienplan_lehrveranstaltung_id_parent]._children)
lvs[lv.studienplan_lehrveranstaltung_id_parent]._children = [];
lvs[lv.studienplan_lehrveranstaltung_id_parent]._children.push(lv);
}
}
res[k] = Object.values(lvs).filter(lv => !lv.studienplan_lehrveranstaltung_id_parent);
let current = res[k];
let index = k.substr(0,1);
const options = {
data: current,
dataTree: true,
dataTreeStartExpanded: true, //start with an expanded tree
dataTreeChildIndent: 15,
layout: "fitDataStretch",
columns: [
{title: this.$p.t('ui', 'bezeichnung'), field: "bezeichnung"},
{title: this.$p.t('lehre','lehrform'), field: "lehrform_kurzbz"},
{title: "ECTS", field: "ects"},
{title: this.$p.t('lehre','note'), field: "note", formatter:(cell, formatterParams, onRendered)=>cell.getValue() || "---"},
{title: (index==1) ? this.$p.t('studierendenantrag','lv_nicht_zulassen') : this.$p.t('studierendenantrag','lv_wiederholen'), field: "antrag_zugelassen", formatter: (cell, formatterParams, onRendered) => {
let data = cell.getData();
if(data._children || !data.zeugnis)
return "";
let input = document.createElement('input');
input.className = "form-check-input";
input.type = "checkbox";
input.role = "switch";
input.checked = cell.getValue();
input.addEventListener('input', () => {
lvs[data.studienplan_lehrveranstaltung_id].antrag_zugelassen = input.checked;
cell.getRow().reformat();
});
if (this.disabled) {
input.disabled = true;
}
let div = document.createElement('div');
div.className = 'form-check form-switch';
div.append(input);
return div;
}},
{
title: this.$p.t('global','anmerkung'),
field: "antrag_anmerkung",
headerSort:false,
titleFormatter:(cell, formatterParams, onRendered)=>{
let link = document.createElement('a');
link.addEventListener('click', (e) => {
e.preventDefault();
});
link.href ="#";
link.title = this.$p.t('studierendenantrag','anmerkung_tooltip');
new bootstrap.Tooltip(link);
let tooltip = document.createElement('span');
tooltip.innerHTML = this.$p.t('global','anmerkung') + " ";
tooltip.append(link);
let icon = document.createElement('i');
link.append(icon);
icon.className = "fa fa-info-circle";
icon.setAttribute("aria-hidden", "true");
icon.style.minWidth = '1em';
return tooltip;
},
formatter: (cell, formatterParams, onRendered) => {
if (this.disabled) {
return cell.getValue() || "";
}
var data = cell.getData();
if (lvs[data.studienplan_lehrveranstaltung_id].antrag_zugelassen)
{
let input = document.createElement('input');
input.className = "form-control";
input.type = "text";
input.value = cell.getValue() || "";
input.addEventListener('input', () => {
lvs[data.studienplan_lehrveranstaltung_id].antrag_anmerkung = input.value;
});
return input;
}
else
{
return "";
}
}
}
]
};
var table = new Tabulator(this.$refs["lvtable" + k.substr(0,1)], options);
}
this.lvs = result.data.retval;
});
}
}
);
},
template: `
<div class="col-sm-10">
<div ref="alertbox"></div>
<span class="d-flex justify-content-between h4">
<span>{{$p.t('studierendenantrag', 'title_lv_nicht_zugelassen')}}</span>
<span>{{lvs1sem}}</span>
</span>
<div ref="lvtable1" class="mb-3">
</div>
<span class="d-flex justify-content-between h4">
<span>{{$p.t('studierendenantrag', 'title_lv_wiederholen')}}</span>
<span>{{lvs2sem}}</span>
</span>
<div ref="lvtable2">
</div>
<button type="button" @click="save" :disabled="isloading || disabled" class="btn btn-primary my-3">{{$p.t('studierendenantrag', 'btn_save_lvs')}}</button>
</div>
<div class="col-sm-2">
<studierendenantrag-status :msg="statusMsg" :severity="statusSeverity"></studierendenantrag-status>
</div>
`
}
@@ -0,0 +1,15 @@
export default {
props: {
msg: String,
severity: String
},
computed: {
severityClass() {
return 'alert-' + this.severity;
}
},
template: `
<div v-if="msg && severity" class="studierendenantrag-status alert text-center mb-3" :class="severityClass" role="alert" v-html="msg">
</div>
`
}
+9 -1
View File
@@ -147,6 +147,14 @@ export const CoreFilterCmpt = {
return columns;
},
fieldIdsForVisibilty() {
if (!this.tableBuilt)
return [];
return this.tabulator.getColumns().filter(col => {
let def = col.getDefinition();
return !def.frozen && def.title;
}).map(col => col.getField());
},
fieldNames() {
if (!this.tableBuilt)
return {};
@@ -539,7 +547,7 @@ export const CoreFilterCmpt = {
:id="'collapseColumns' + idExtra"
class="card-body collapse"
:data-bs-parent="'#filterCollapsables' + idExtra"
:fields="fields"
:fields="fieldIdsForVisibilty"
:selected="selectedFields"
:names="fieldNames"
@hide="tabulator.hideColumn($event)"
@@ -38,9 +38,6 @@ export default {
return {
selectedFields: []
};
},
computed: {
},
watch: {
selected(n) {
@@ -68,7 +65,7 @@ export default {
<div
v-for="fieldToDisplay in fields"
class="btn"
:class="selected.indexOf(fieldToDisplay) != -1 ? 'btn-dark' : 'btn-outline-dark' "
:class="selectedFields.indexOf(fieldToDisplay) != -1 ? 'btn-dark' : 'btn-outline-dark' "
@click="toggle(fieldToDisplay)"
>
{{ names[fieldToDisplay] || fieldToDisplay }}
@@ -1,6 +1,9 @@
const ANRECHNUNGSTATUS_APPROVED = 'approved';
const ANRECHNUNGSTATUS_REJECTED = 'rejected';
const HERKUNFT_DER_KENNTNISSE_MAX_LENGTH = 125;
const CHAR_LENGTH125 = 125;
const CHAR_LENGTH150 = 150;
const CHAR_LENGTH500 = 500;
const CHAR_LENGTH1000 = 1000;
const COLOR_DANGER = '#f2dede';
@@ -25,7 +28,7 @@ $(function(){
// Alert message inside Begruendungsbox, if maximum ECTS exceeded
requestAnrechnung.alertIfMaxEctsExceededInsideBegruendungsbox();
// Set chars counter for textarea 'Herkunft der Kenntnisse'
// Set chars counter for textareas
requestAnrechnung.setCharsCounter();
// If Sperregrund exists: display Sperre panel, hide Status panel and disable all form elements
@@ -98,6 +101,8 @@ $(function(){
begruendung: this.begruendung.value,
lv_id: this.lv_id.value,
studiensemester: this.studiensemester.value,
begruendung_ects: this.begruendung_ects.value,
begruendung_lvinhalt: this.begruendung_lvinhalt.value,
bestaetigung: this.bestaetigung.value,
uploadfile: this.uploadfile.files
},
@@ -207,11 +212,22 @@ var requestAnrechnung = {
},
setCharsCounter: function(){
$('#requestAnrechnung-herkunftDerKenntnisse').keyup(function() {
let length = HERKUNFT_DER_KENNTNISSE_MAX_LENGTH - $(this).val().length;
let length = CHAR_LENGTH125 - $(this).val().length;
$('#requestAnrechnung-herkunftDerKenntnisse-charCounter').text(length);
});
$('#requestAnrechnung-begruendungEcts').keyup(function() {
let length = CHAR_LENGTH150 - $(this).val().length;
$('#requestAnrechnung-begruendungEcts-charCounter').text(length);
});
$('#requestAnrechnung-begruendungLvinhalt').keyup(function() {
let maxlength = CHAR_LENGTH1000 - $(this).val().length;
$('#requestAnrechnung-begruendungLvinhalt-charCounterMax').text(maxlength);
let minlength = CHAR_LENGTH500 - $(this).val().length;
$('#requestAnrechnung-begruendungLvinhalt-charCounterMin').text(minlength);
});
},
formatAnrechnungIsApplied: function (antragdatum, dms_id, filename){
$('#requestAnrechnung-antragdatum').text(antragdatum);
+105
View File
@@ -0,0 +1,105 @@
const categories = {};
const loadingModules = {};
function extractCategory(obj, category) {
return obj.filter(e => e.category == category).reduce((res, elem) => {
if (!res[elem.phrase])
res[elem.phrase] = elem.text;
return res;
}, {});
}
function reloadRefs(category) {
while (loadingModules[category].length) {
var v = loadingModules[category].pop();
v[0].value = getValueForLoadedPhrase(category, v[1], v[2]);
Vue.triggerRef(v[0]);
/*Vue.unref(v);*/
}
}
function loadLazy(category, val, phrase, params) {
// NOTE(chris): load module if it's not loaded yet
if (loadingModules[category]) {
loadingModules[category].push([val, phrase, params]);
if (categories[category]) // NOTE(chris): this is for safety in case the loading finished the moment before the val was pushed into the array
reloadRefs(category);
return;
}
loadingModules[category] = [[val, phrase, params]];
axios.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Phrasen/loadModule/' + category).then(res => {
if (res.data.retval)
categories[category] = extractCategory(res.data.retval, category);
else
categories[category] = {};
reloadRefs(category);
}).catch(err => console.error(err));
}
function getValueForLoadedPhrase(category, phrase, params) {
let result = categories[category][phrase];
if (!result)
return '<< PHRASE ' + phrase + '>>';
if (params)
result = result.replace(/\{([^}]*)\}/g, (match, p1) => params[p1] === undefined ? match : params[p1]);
return result;
}
const phrasen = {
t_ref(category, phrase, params) {
if (params === undefined && (
(Array.isArray(category) && category.length == 2) ||
(category.split && category.split('/').length == 2))
) {
params = phrase;
[category, phrase] = category.split ? category.split('/') : category;
}
if (phrase === undefined) {
console.error('invalid input');
return '';
}
if (!categories[category]) {
var initialval = '';
if (window.FHC_JS_PHRASES_STORAGE_OBJECT !== undefined) {
var tmp_category = extractCategory(FHC_JS_PHRASES_STORAGE_OBJECT, category);
if(tmp_category[phrase] !== undefined ) {
initialval = tmp_category[phrase];
}
}
if (!categories[category] || Object.keys(categories[category]).length === 0) {
categories[category] = undefined;
let val = Vue.ref(initialval);
loadLazy(category, val, phrase, params);
return val;
}
}
var result = getValueForLoadedPhrase(category, phrase, params);
return Vue.ref(result);
},
t(category, phrase, params) {
return Vue.unref(this.t_ref(category, phrase, params));
}
};
export default {
data: () => {
return {
p: phrasen
}
}
}
// Composable (wrapper for mixin)
export function usePhrasen() {
function t(category, phrase, params) {
return phrasen.t(category, phrase, params);
}
return {
t,
}
}
+67
View File
@@ -0,0 +1,67 @@
const categories = Vue.reactive({});
const loadingModules = {};
function extractCategory(obj, category) {
return obj.filter(e => e.category == category).reduce((res, elem) => {
if (!res[elem.phrase])
res[elem.phrase] = elem.text;
return res;
}, {});
}
function getValueForLoadedPhrase(category, phrase, params) {
let result = categories[category][phrase];
if (!result)
return '<< PHRASE ' + phrase + '>>';
if (params)
result = result.replace(/\{([^}]*)\}/g, (match, p1) => params[p1] === undefined ? match : params[p1]);
return result;
}
const phrasen = {
loadCategory(category) {
if (Array.isArray(category))
return Promise.all(category.map(cat => this.loadCategory(cat)));
if (!loadingModules[category])
loadingModules[category] = axios
.get(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/components/Phrasen/loadModule/' + category)
.then(res => {
if (res.data.retval)
categories[category] = extractCategory(res.data.retval, category);
else
categories[category] = {};
});
return loadingModules[category];
},
t_ref(category, phrase, params) {
console.warn('depricated');
return Vue.computed(() => this.t(category, phrase, params));
},
t(category, phrase, params) {
if (params === undefined && (
(Array.isArray(category) && category.length == 2) ||
(category.split && category.split('/').length == 2))
) {
params = phrase;
[category, phrase] = category.split ? category.split('/') : category;
}
if (phrase === undefined) {
console.error('invalid input', category, phrase, params);
return '';
}
let val = Vue.computed(() => {
if (!categories[category])
return '';
return getValueForLoadedPhrase(category, phrase, params);
});
if (!categories[category])
this.loadCategory(category);
return val.value;
}
};
export default {
install(app, options) {
app.config.globalProperties.$p = phrasen;
}
}
+43
View File
@@ -0,0 +1,43 @@
if (!primevue) {
console.error('PrimeVue not loaded!');
}
// NOTE(chris): Click on clear button gives an error. This is a bug in primevue => fixed in current version
Tabulator.extendModule('filter', 'filters', {
"dates": (headerValue, rowValue) => {
if (!headerValue)
return true;
let v = new Date(rowValue);
if (Array.isArray(headerValue)) {
if (headerValue[1]) {
return v >= headerValue[0] && v <= headerValue[1].setHours(23, 59, 59, 999);
}
return v.toDateString() == headerValue[0].toDateString();
}
return v.toDateString() == headerValue.toDateString();
}
});
function dateFilter(cell, onRendered, success) {
let div = document.createElement('div');
Vue.createApp({
components: {
PrimevueCalendar: primevue.calendar
},
data() {
return {
val: null
}
},
watch: {
val(n) {
success(n);
}
},
template: `<primevue-calendar v-model="val" selection-mode="range" :manual-input="false" show-button-bar></primevue-calendar>`
}).use(primevue.config.default).mount(div);
return div;
}
export { dateFilter as 'dateFilter' };