mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-26 09:04:28 +00:00
Merge branch 'master' into feature-25999/C4_TEST
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import Phrasen from '../../plugin/Phrasen.js';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
modal: null
|
||||
@@ -88,6 +90,7 @@ export default {
|
||||
}
|
||||
});
|
||||
const wrapper = document.createElement("div");
|
||||
instance.use(Phrasen); // TODO(chris): find a more dynamic way
|
||||
instance.mount(wrapper);
|
||||
document.body.appendChild(wrapper);
|
||||
});
|
||||
|
||||
@@ -99,8 +99,10 @@ export const CoreFetchCmpt = {
|
||||
*
|
||||
*/
|
||||
errorHandler: function(error) {
|
||||
if (error.response.data.retval)
|
||||
if (error.response?.data?.retval)
|
||||
this.setError(error.response.data.retval);
|
||||
else if (error.data?.message)
|
||||
this.setError(error.data.message);
|
||||
else
|
||||
this.setError(error.message);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import FhcFragment from "../Fragment.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FhcFragment
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
$registerToForm: component => {
|
||||
if (this.inputs.indexOf(component) < 0)
|
||||
this.inputs.push(component);
|
||||
},
|
||||
$clearValidationForName: this.clearValidationForName
|
||||
};
|
||||
},
|
||||
props: {
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'form'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputs: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sortedInputs() {
|
||||
return this.inputs.reduce((a,c) => {
|
||||
let name = c.name || '_default';
|
||||
if (!a[name])
|
||||
a[name] = [];
|
||||
a[name].push(c);
|
||||
|
||||
if (c.lcType == 'checkbox' && name.substr(-1) == ']' && name.indexOf('[')) {
|
||||
name = name.substr(0, name.lastIndexOf('['));
|
||||
if (!a[name])
|
||||
a[name] = [];
|
||||
a[name].push(c);
|
||||
}
|
||||
|
||||
return a;
|
||||
}, {});
|
||||
},
|
||||
factory() {
|
||||
const factory = Object.create(Object.getPrototypeOf(this.$fhcApi.factory), Object.getOwnPropertyDescriptors(this.$fhcApi.factory));
|
||||
factory.$fhcApi = {
|
||||
get: this.get,
|
||||
post: this.post,
|
||||
_defaultErrorHandlers: this.$fhcApi._defaultErrorHandlers
|
||||
};
|
||||
return factory;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
get(...args) {
|
||||
if (typeof args[0] == 'object' && args[0].clearValidation && args[0].setFeedback)
|
||||
args[0] = this;
|
||||
else
|
||||
args.unshift(this);
|
||||
|
||||
return this.$fhcApi.get(...args);
|
||||
},
|
||||
post(...args) {
|
||||
if (typeof args[0] == 'object' && args[0].clearValidation && args[0].setFeedback)
|
||||
args[0] = this;
|
||||
else
|
||||
args.unshift(this);
|
||||
|
||||
return this.$fhcApi.post(...args);
|
||||
},
|
||||
_sendFeedbackToInput(inputs, feedback, valid) {
|
||||
if (inputs.length) {
|
||||
inputs.forEach(input => input.setFeedback(valid, feedback));
|
||||
return false;
|
||||
}
|
||||
if (this.$fhcAlert) {
|
||||
this.$fhcAlert[valid ? 'alertSuccess' : 'alertError'](feedback);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
setFeedback(valid, feedback) {
|
||||
if (Array.isArray(feedback)) {
|
||||
let remaining = feedback.filter(fb =>
|
||||
this._sendFeedbackToInput(
|
||||
this.sortedInputs['_default'] || [],
|
||||
fb,
|
||||
valid
|
||||
)
|
||||
);
|
||||
return remaining.length ? remaining : null;
|
||||
}
|
||||
if (typeof feedback === 'object') {
|
||||
let remaining = Object.entries(feedback).filter(([name, fb]) =>
|
||||
this._sendFeedbackToInput(
|
||||
this.sortedInputs[name.split('.')[0] + name.split('.').slice(1).map(p => `[${p}]`).join("")] || this.sortedInputs['_default'] || [],
|
||||
fb,
|
||||
valid
|
||||
)
|
||||
);
|
||||
return remaining.length ? Object.fromEntries(remaining) : null;
|
||||
}
|
||||
|
||||
let remaining = this._sendFeedbackToInput(
|
||||
this.sortedInputs['_default'] || [],
|
||||
feedback,
|
||||
valid
|
||||
);
|
||||
return remaining ? feedback : null;
|
||||
},
|
||||
clearValidation() {
|
||||
this.inputs.forEach(input => input.clearValidation());
|
||||
},
|
||||
clearValidationForName(name) {
|
||||
(this.sortedInputs[name.split('.')[0] + name.split('.').slice(1).map(p => `[${p}]`).join("")] || this.sortedInputs['_default'] || [])
|
||||
.forEach(input => input.clearValidation());
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<component :is="tag || 'FhcFragment'" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</component>`
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import FhcFragment from "../Fragment.js";
|
||||
|
||||
let _uuid = {};
|
||||
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
FhcFragment
|
||||
},
|
||||
inject: {
|
||||
registerToForm: {
|
||||
from: '$registerToForm',
|
||||
default: null
|
||||
},
|
||||
clearValidationForName: {
|
||||
from: '$clearValidationForName',
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
bsFeedback: Boolean,
|
||||
noAutoClass: Boolean,
|
||||
noFeedback: Boolean,
|
||||
inputGroup: Boolean,
|
||||
type: String,
|
||||
name: String,
|
||||
containerClass: [String, Array, Object]
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valid: undefined,
|
||||
feedback: [],
|
||||
modelValueDummy: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasContainer() {
|
||||
if (!this.bsFeedback)
|
||||
return true;
|
||||
if (this.containerClass)
|
||||
return true;
|
||||
for (const prop in this.autoContainerClass)
|
||||
if (Object.hasOwn(this.autoContainerClass, prop))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
acc() {
|
||||
if (!this.containerClass)
|
||||
return {};
|
||||
if (typeof this.containerClass === 'string' || this.containerClass instanceof String)
|
||||
return this.containerClass.split(' ').reduce((a,c) => {a[c] = true; return a}, {});
|
||||
if (Array.isArray(this.containerClass))
|
||||
return this.containerClass.reduce((a,c) => {a[c] = true; return a}, {});
|
||||
return this.containerClass;
|
||||
},
|
||||
autoContainerClass() {
|
||||
if (this.noAutoClass)
|
||||
return this.acc;
|
||||
|
||||
const acc = {...this.acc};
|
||||
|
||||
if (this.inputGroup)
|
||||
acc['input-group-item'] = true;
|
||||
|
||||
if (this.lcType == 'radio' || this.lcType == 'checkbox')
|
||||
acc['form-check'] = true;
|
||||
|
||||
if (this.inputGroup && acc['form-check']) {
|
||||
acc['input-group-item'] = false;
|
||||
acc['form-check'] = false;
|
||||
acc['input-group-text'] = true;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
lcType() {
|
||||
if (!this.type)
|
||||
return 'text';
|
||||
return this.type.toLowerCase();
|
||||
},
|
||||
tag() {
|
||||
switch (this.lcType) {
|
||||
case 'textarea':
|
||||
case 'select':
|
||||
return this.lcType;
|
||||
case 'datepicker':
|
||||
return 'VueDatePicker';
|
||||
case 'autocomplete':
|
||||
return 'PvAutocomplete';
|
||||
case 'uploadimage':
|
||||
return 'UploadImage';
|
||||
case 'uploadfile':
|
||||
case 'uploaddms':
|
||||
return 'UploadDms';
|
||||
default:
|
||||
return 'input';
|
||||
}
|
||||
},
|
||||
validationClass() {
|
||||
const classes = [];
|
||||
if (this.valid)
|
||||
classes.push('is-valid');
|
||||
else if (this.valid === false)
|
||||
classes.push('is-invalid');
|
||||
|
||||
if (!this.noAutoClass) {
|
||||
let c = this.$attrs.class ? this.$attrs.class.split(' ') : [];
|
||||
switch (this.lcType) {
|
||||
// TODO(chris): complete list!
|
||||
case 'select':
|
||||
if (!c.includes('form-select'))
|
||||
classes.push('form-select');
|
||||
break;
|
||||
case 'range':
|
||||
if (!c.includes('form-range'))
|
||||
classes.push('form-range');
|
||||
break;
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
// TODO(chris): maybe different handling?
|
||||
if (!c.includes('form-check-input') && !c.includes('btn-check'))
|
||||
classes.push('form-check-input');
|
||||
break;
|
||||
case 'color':
|
||||
if (!c.includes('form-control-color'))
|
||||
classes.push('form-control-color');
|
||||
if (!c.includes('form-control'))
|
||||
classes.push('form-control');
|
||||
break;
|
||||
case 'autocomplete':
|
||||
case 'datepicker':
|
||||
classes.push('p-0');
|
||||
classes.push('border-0');
|
||||
if (!c.includes('form-control'))
|
||||
classes.push('form-control');
|
||||
break;
|
||||
case 'text':
|
||||
case 'number':
|
||||
case 'password':
|
||||
case 'textarea':
|
||||
if (!c.includes('form-control'))
|
||||
classes.push('form-control');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return classes;
|
||||
},
|
||||
feedbackClass() {
|
||||
if (!this.feedback || this.feedback === true)
|
||||
return '';
|
||||
if (!this.bsFeedback)
|
||||
return {
|
||||
'valid-tooltip': this.valid === true,
|
||||
'invalid-tooltip': this.valid === false
|
||||
};
|
||||
return {
|
||||
'valid-feedback': this.valid === true,
|
||||
'invalid-feedback': this.valid === false
|
||||
};
|
||||
},
|
||||
modelValueCmp: {
|
||||
get() {
|
||||
if (this.$attrs.modelValue === undefined)
|
||||
return this.modelValueDummy;
|
||||
return this.$attrs.modelValue;
|
||||
},
|
||||
set(v) {
|
||||
if (this.$attrs.modelValue === undefined)
|
||||
this.modelValueDummy = v;
|
||||
this.$emit('update:modelValue', v);
|
||||
}
|
||||
},
|
||||
idCmp() {
|
||||
let uuid = this.$attrs.id;
|
||||
if (this.lcType == 'datepicker')
|
||||
uuid = this.$attrs.uid;
|
||||
if (!uuid && this.$attrs.label)
|
||||
uuid = 'fhc-form-input';
|
||||
if (!uuid)
|
||||
return undefined;
|
||||
if (this.lcType == 'datepicker')
|
||||
uuid = 'dp-input-' + uuid;
|
||||
if (_uuid[uuid] === undefined)
|
||||
_uuid[uuid] = 0;
|
||||
return uuid + '-' + (_uuid[uuid]++);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearValidation() {
|
||||
this.valid = undefined;
|
||||
this.feedback = [];
|
||||
},
|
||||
clearValidationForThisName() {
|
||||
if (this.valid === undefined)
|
||||
return;
|
||||
if (this.clearValidationForName && this.name)
|
||||
this.clearValidationForName(this.name);
|
||||
else
|
||||
this.clearValidation();
|
||||
},
|
||||
setFeedback(valid, feedback) {
|
||||
if (!feedback)
|
||||
feedback = [];
|
||||
if (!Array.isArray(feedback))
|
||||
feedback = [feedback];
|
||||
this.valid = valid;
|
||||
// NOTE(chris): On a list of radios/checkboxes only add the feedback message to the last item
|
||||
if (this.name && (this.lcType == 'radio' || this.lcType == 'checkbox')) {
|
||||
const selector = 'input[type="' + this.lcType + '"][name="' + this.name + '"]';
|
||||
if ([...this.$el.parentNode.querySelectorAll(selector)].pop() != this.$refs.input)
|
||||
return;
|
||||
}
|
||||
this.feedback = feedback;
|
||||
},
|
||||
_loadComponents() {
|
||||
if (this.tag == 'VueDatePicker' && !this._.components.VueDatePicker) {
|
||||
this._.components.VueDatePicker = Vue.defineAsyncComponent(() => import("../vueDatepicker.js.php"));
|
||||
} else if (this.tag == 'PvAutocomplete' && !this._.components.PvAutocomplete) {
|
||||
this._.components.PvAutocomplete = Vue.defineAsyncComponent(() => import(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/public/js/components/primevue/autocomplete/autocomplete.esm.min.js"));
|
||||
} else if (this.tag == 'UploadImage' && !this._.components.UploadImage) {
|
||||
this._.components.UploadImage = Vue.defineAsyncComponent(() => import("./Upload/Image.js"));
|
||||
} else if (this.tag == 'UploadDms' && !this._.components.UploadDms) {
|
||||
this._.components.UploadDms = Vue.defineAsyncComponent(() => import("./Upload/Dms.js"));
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this._loadComponents();
|
||||
},
|
||||
beforeUpdate() {
|
||||
this._loadComponents();
|
||||
},
|
||||
mounted() {
|
||||
if (this.registerToForm)
|
||||
this.registerToForm(this);
|
||||
},
|
||||
template: `
|
||||
<component :is="!hasContainer ? 'FhcFragment' : 'div'" class="position-relative" :class="autoContainerClass">
|
||||
<label v-if="$attrs.label && lcType != 'radio' && lcType != 'checkbox'" :for="idCmp">{{$attrs.label}}</label>
|
||||
<input v-if="tag == 'input'" :type="lcType" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)">
|
||||
<textarea v-else-if="tag == 'textarea'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)"></textarea>
|
||||
<select v-else-if="tag == 'select'" ref="input" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidationForThisName(); $emit('input', $event)">
|
||||
<slot></slot>
|
||||
</select>
|
||||
<component
|
||||
v-else-if="tag == 'VueDatePicker'"
|
||||
ref="input"
|
||||
:is="tag"
|
||||
:type="type"
|
||||
v-model="modelValueCmp"
|
||||
v-bind="$attrs"
|
||||
:uid="idCmp ? idCmp.substr(9) : idCmp"
|
||||
:name="name"
|
||||
:class="validationClass"
|
||||
:input-class-name=
|
||||
"[...Object.entries({'form-control': !noAutoClass, 'is-valid': valid === true, 'is-invalid': valid === false}).reduce((a,[k,v]) => {if(v) a.push(k);return a}, []), ...($attrs['input-class-name'] ? $attrs['input-class-name'].split(' ') : [])].join(' ')"
|
||||
@update:model-value="clearValidationForThisName"
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
<component
|
||||
v-else-if="tag == 'PvAutocomplete'"
|
||||
ref="input"
|
||||
:is="tag"
|
||||
:type="type"
|
||||
v-model="modelValueCmp"
|
||||
v-bind="$attrs"
|
||||
:id="idCmp"
|
||||
:input-props="{name}"
|
||||
:class="validationClass"
|
||||
:input-class="[...Object.entries({'form-control': !noAutoClass, 'is-valid': valid === true, 'is-invalid': valid === false}).reduce((a,[k,v]) => {if(v) a.push(k);return a}, []), ...($attrs['input-class'] ? $attrs['input-class'].split(' ') : [])].join(' ')"
|
||||
@update:model-value="clearValidationForThisName"
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
<component
|
||||
v-else-if="tag == 'UploadDms'"
|
||||
ref="input"
|
||||
:is="tag"
|
||||
:type="type"
|
||||
v-model="modelValueCmp"
|
||||
v-bind="$attrs"
|
||||
:id="idCmp"
|
||||
:name="name"
|
||||
:class="validationClass"
|
||||
:input-class="validationClass"
|
||||
:no-list="inputGroup"
|
||||
@update:model-value="clearValidationForThisName"
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
<component
|
||||
v-else
|
||||
ref="input"
|
||||
:is="tag"
|
||||
:type="type"
|
||||
v-model="modelValueCmp"
|
||||
v-bind="$attrs"
|
||||
:id="idCmp"
|
||||
:name="name"
|
||||
:class="validationClass"
|
||||
@update:model-value="clearValidationForThisName"
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
<label v-if="$attrs.label && (lcType == 'radio' || lcType == 'checkbox')" :for="idCmp" :class="!noAutoClass && 'form-check-label'">{{$attrs.label}}</label>
|
||||
<div v-if="valid !== undefined && feedback.length && !noFeedback" :class="feedbackClass">
|
||||
<template v-for="(msg, i) in feedback" :key="i">
|
||||
<hr v-if="i" class="m-0">
|
||||
{{msg}}
|
||||
</template>
|
||||
</div>
|
||||
</component>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
export default {
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [ FileList, Array ],
|
||||
required: true
|
||||
},
|
||||
multiple: Boolean,
|
||||
id: String,
|
||||
name: String,
|
||||
inputClass: [String, Array, Object],
|
||||
noList: Boolean
|
||||
},
|
||||
methods: {
|
||||
stringifyFile(file) {
|
||||
return JSON.stringify({
|
||||
lastModified: file.lastModified,
|
||||
lastModifiedDate: file.lastModifiedDate,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type
|
||||
});
|
||||
},
|
||||
addFiles(event) {
|
||||
if (!this.multiple)
|
||||
return this.$emit('update:modelValue', event.target.files);
|
||||
|
||||
const dt = new DataTransfer();
|
||||
const doubles = [];
|
||||
for (var file of this.modelValue) {
|
||||
dt.items.add(file);
|
||||
doubles.push(this.stringifyFile(file));
|
||||
}
|
||||
for (var file of event.target.files) {
|
||||
// NOTE(chris): deep check (with FileReader) would require an async function so we only check the basic attributes
|
||||
if (doubles.indexOf(this.stringifyFile(file)) < 0)
|
||||
dt.items.add(file);
|
||||
}
|
||||
this.$emit('update:modelValue', dt.files);
|
||||
},
|
||||
removeFile(id) {
|
||||
const fileToRemove = Array.from(this.modelValue)[id];
|
||||
|
||||
const dt = new DataTransfer();
|
||||
for (var file of this.modelValue) {
|
||||
if (file !== fileToRemove)
|
||||
dt.items.add(file);
|
||||
}
|
||||
this.$emit('update:modelValue', dt.files);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(n) {
|
||||
if (n instanceof FileList)
|
||||
return this.$refs.upload.files = n;
|
||||
|
||||
const dt = new DataTransfer();
|
||||
const dms = [];
|
||||
for (var file of n) {
|
||||
if (file instanceof File) {
|
||||
dt.items.add(file);
|
||||
} else {
|
||||
const dmsFile = new File([JSON.stringify(file)], file.name, {
|
||||
type: 'application/x.fhc-dms+json'
|
||||
});
|
||||
dt.items.add(dmsFile);
|
||||
}
|
||||
}
|
||||
this.$emit('update:modelValue', dt.files);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="form-upload-dms">
|
||||
<input ref="upload" class="form-control" :class="inputClass" :id="id" :name="name" :multiple="multiple" type="file" @change="addFiles">
|
||||
<ul v-if="modelValue.length && multiple && !noList" class="list-unstyled m-0">
|
||||
<li v-for="(file, index) in modelValue" :key="index" class="d-flex mx-1 mt-1 align-items-start">
|
||||
<span class="col-auto"><i class="fa fa-file me-1"></i></span>
|
||||
<span class="col">{{ file.name }}</span>
|
||||
<button class="col-auto btn btn-outline-secondary btn-p-0" @click="removeFile(index)">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
export default {
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
props: {
|
||||
modelValue: String
|
||||
},
|
||||
computed: {
|
||||
valueAsBase64DataString() {
|
||||
if (!this.modelValue || this.modelValue.substring(0, 10) == 'data:image')
|
||||
return this.modelValue;
|
||||
return 'data:image/jpeg;charset=utf-8;base64,' + this.modelValue;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openUploadDialog() {
|
||||
this.$refs.fileInput.click();
|
||||
},
|
||||
pickFile() {
|
||||
let file = this.$refs.fileInput.files;
|
||||
if (file && file[0]) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
this.$emit('update:modelValue', e.target.result);
|
||||
}
|
||||
reader.readAsDataURL(file[0]);
|
||||
}
|
||||
},
|
||||
deleteImage() {
|
||||
this.$emit('update:modelValue', '');
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="form-upload-image">
|
||||
<template v-if="modelValue">
|
||||
<img class="img-thumbnail" :src="valueAsBase64DataString" />
|
||||
<div class="fotobutton">
|
||||
<div class="d-grid gap-2 d-md-flex">
|
||||
<button type="button" class="btn btn-outline-dark btn-sm" @click="deleteImage">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-dark btn-sm" @click="openUploadDialog">
|
||||
<i class="fa fa-pen"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot>
|
||||
<svg class="bd-placeholder-img img-thumbnail" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera: 200x200" preserveAspectRatio="xMidYMid slice" focusable="false"><title>A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera</title><rect width="100%" height="100%" fill="#868e96"></rect><text x="50%" y="50%" fill="#dee2e6" dy=".3em"></text></svg>
|
||||
</slot>
|
||||
<div class="fotobutton-visible">
|
||||
<div class="d-grid gap-2 d-md-flex">
|
||||
<button type="button" class="btn btn-outline-dark btn-sm" @click="openUploadDialog">
|
||||
<i class="fa fa-pen"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<input :id="$attrs.id" class="d-none" type="file" ref="fileInput" @input="pickFile" accept="image/*">
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
export default {
|
||||
inject: [
|
||||
'$registerToForm'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
feedback: {
|
||||
success: [],
|
||||
danger: []
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clearValidation() {
|
||||
this.feedback = {
|
||||
success: [],
|
||||
danger: []
|
||||
};
|
||||
},
|
||||
setFeedback(valid, feedback) {
|
||||
if (!feedback)
|
||||
feedback = [];
|
||||
if (!Array.isArray(feedback))
|
||||
feedback = [feedback];
|
||||
const ts = Date.now();
|
||||
this.feedback[valid ? 'success' : 'danger'] = feedback.map(msg => [msg, ts]);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$registerToForm)
|
||||
this.$registerToForm(this);
|
||||
},
|
||||
template: `
|
||||
<template v-for="(arr, key) in feedback" :key="key">
|
||||
<div v-for="[msg, ts] in arr" :key="ts + msg" class="alert alert-dismissible fade show" :class="'alert-' + key" role="alert">
|
||||
{{msg}}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
</template>
|
||||
`
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
render() {
|
||||
return (this.$slots && this.$slots.default) ? this.$slots.default() : null;
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import StudierendenantragAbmeldung from './Form/Abmeldung.js';
|
||||
import StudierendenantragAbmeldungStgl from './Form/AbmeldungStgl.js';
|
||||
import StudierendenantragUnterbrechung from './Form/Unterbrechung.js';
|
||||
import StudierendenantragWiederholung from './Form/Wiederholung.js';
|
||||
import Phrasen from '../../mixins/Phrasen.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -11,9 +10,6 @@ export default {
|
||||
StudierendenantragUnterbrechung,
|
||||
StudierendenantragWiederholung
|
||||
},
|
||||
mixins: [
|
||||
Phrasen
|
||||
],
|
||||
emits: [
|
||||
'update:infoArray',
|
||||
'update:statusMsg',
|
||||
@@ -37,13 +33,13 @@ export default {
|
||||
return 'Studierendenantrag' + this.antragType;
|
||||
},
|
||||
infoText() {
|
||||
return this.p.t('studierendenantrag/info_' + this.antragType + '_' + this.status);
|
||||
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)}}
|
||||
{{$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>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import {CoreFetchCmpt} from '../../Fetch.js';
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
import CoreForm from '../../Form/Form.js';
|
||||
import FormValidation from '../../Form/Validation.js';
|
||||
import FormInput from '../../Form/Input.js';
|
||||
|
||||
var _uuid = 0;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoreFetchCmpt
|
||||
CoreFetchCmpt,
|
||||
CoreForm,
|
||||
FormValidation,
|
||||
FormInput
|
||||
},
|
||||
mixins: [
|
||||
Phrasen
|
||||
],
|
||||
emits: [
|
||||
'setInfos',
|
||||
'setStatus'
|
||||
@@ -22,9 +24,8 @@ export default {
|
||||
return {
|
||||
data: null,
|
||||
saving: false,
|
||||
errors: {
|
||||
grund: [],
|
||||
default: []
|
||||
formData: {
|
||||
grund: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -33,142 +34,111 @@ export default {
|
||||
switch (this.data.status)
|
||||
{
|
||||
case 'Erstellt': return 'info';
|
||||
case 'Pause':
|
||||
case 'Zurueckgezogen': return 'danger';
|
||||
case 'Genehmigt': return 'success';
|
||||
default: return 'info';
|
||||
default: return 'warning';
|
||||
}
|
||||
},
|
||||
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;
|
||||
return this.$fhcApi.factory
|
||||
.studstatus.abmeldung.getDetails(this.studierendenantragId, this.prestudentId)
|
||||
.then(result => {
|
||||
this.data = result.data;
|
||||
if (this.data.status) {
|
||||
const msg = (this.data.status == 'Pause' && this.data.status_insertvon == "Studienabbruch") ? Vue.computed(() => {
|
||||
let status = this.$p.t('studierendenantrag/status_stop');
|
||||
return this.$p.t('studierendenantrag', 'status_x', {status});
|
||||
}) : Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp}));
|
||||
this.$emit("setStatus", {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
msg,
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
createAntrag() {
|
||||
bootstrap.Modal.getOrCreateInstance(this.$refs.modal).hide();
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_saving')}),
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_error')}),
|
||||
severity: 'danger'
|
||||
|
||||
this.$refs.form.clearValidation();
|
||||
this.$refs.form.factory
|
||||
.studstatus.abmeldung.create(
|
||||
this.data.studiensemester_kurzbz,
|
||||
this.data.prestudent_id,
|
||||
this.formData.grund
|
||||
)
|
||||
.then(result => {
|
||||
if (result.data === true)
|
||||
document.location += "";
|
||||
|
||||
this.data = result.data;
|
||||
if (this.data.status)
|
||||
this.$emit("setStatus", {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result.data.retval === true)
|
||||
document.location += "";
|
||||
this.data = result.data.retval;
|
||||
if (this.data.status) {
|
||||
this.$emit("setStatus", {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
else
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_open')}),
|
||||
severity:'success'
|
||||
});
|
||||
}
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_open')})),
|
||||
severity:'success'
|
||||
});
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
|
||||
severity: 'danger'
|
||||
});
|
||||
this.saving = false;
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
},
|
||||
cancelAntrag() {
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_cancelling')}),
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_error')}),
|
||||
severity:'danger'
|
||||
|
||||
this.$refs.form.clearValidation();
|
||||
this.$refs.form.factory
|
||||
.studstatus.abmeldung.cancel(
|
||||
this.data.studierendenantrag_id
|
||||
)
|
||||
.then(result => {
|
||||
if (Number.isInteger(result.data))
|
||||
document.location = document.location.replace(/abmeldung\/([0-9]*)\/[0-9]*[\/]?$/, 'abmeldung/$1') + "/" + result.data;
|
||||
|
||||
this.data = result.data;
|
||||
if (this.data.status)
|
||||
this.$emit("setStatus", {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
else
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_cancelled')}),
|
||||
severity: 'danger'
|
||||
});
|
||||
}
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_cancelled')})),
|
||||
severity: 'danger'
|
||||
});
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
|
||||
severity: 'danger'
|
||||
});
|
||||
this.saving = false;
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -177,55 +147,55 @@ export default {
|
||||
template: `
|
||||
<div class="studierendenantrag-form-abmeldung">
|
||||
<core-fetch-cmpt :api-function="load">
|
||||
<div class="row">
|
||||
<core-form ref="form" class="row">
|
||||
<div class="col-12">
|
||||
<div v-for="error in errors.default" class="alert alert-danger" role="alert" v-html="error">
|
||||
</div>
|
||||
<form-validation></form-validation>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'studiengang')}}</th>
|
||||
<th>{{$p.t('lehre', 'studiengang')}}</th>
|
||||
<td align="right">{{data.bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'organisationsform')}}</th>
|
||||
<th>{{$p.t('lehre', 'organisationsform')}}</th>
|
||||
<td align="right">{{data.orgform_bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<td align="right">{{data.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('person', 'personenkennzeichen')}}</th>
|
||||
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
|
||||
<td align="right">{{data.matrikelnr}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'studienjahr')}}</th>
|
||||
<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>
|
||||
<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>
|
||||
<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'"
|
||||
<form-input
|
||||
type="textarea"
|
||||
label="Grund:"
|
||||
v-model="formData.grund"
|
||||
name="grund"
|
||||
rows="5"
|
||||
:disabled="saving"
|
||||
ref="grund"
|
||||
required
|
||||
></textarea>
|
||||
<div v-if="errors.grund.length" class="invalid-feedback">
|
||||
{{errors.grund.join(".")}}
|
||||
</div>
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
<div class="col-12 text-end">
|
||||
<button
|
||||
@@ -235,7 +205,7 @@ export default {
|
||||
@click="cancelAntrag"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{p.t('studierendenantrag', 'btn_cancel')}}
|
||||
{{$p.t('studierendenantrag', 'btn_cancel')}}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="!data.studierendenantrag_id"
|
||||
@@ -245,7 +215,7 @@ export default {
|
||||
:data-bs-target="'#studierendenantrag-form-abmeldung-' + uuid + '-modal'"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{p.t('studierendenantrag', 'btn_create_Abmeldung')}}
|
||||
{{$p.t('studierendenantrag', 'btn_create_Abmeldung')}}
|
||||
</button>
|
||||
|
||||
<div
|
||||
@@ -262,31 +232,31 @@ export default {
|
||||
class="modal-title"
|
||||
:id="'studierendenantrag-form-abmeldung-' + uuid + '-modal-label'"
|
||||
>
|
||||
{{p.t('studierendenantrag', 'title_Abmeldung')}}
|
||||
{{$p.t('studierendenantrag', 'title_Abmeldung')}}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="p.t('ui', 'schliessen')"></button>
|
||||
<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 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')}}
|
||||
{{$p.t('studierendenantrag', 'btn_create_Abmeldung')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</core-form>
|
||||
|
||||
<template v-slot:error="{errorMessage}">
|
||||
<div class="alert alert-danger m-0" role="alert">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</template>
|
||||
</core-fetch-cmpt>
|
||||
</div>
|
||||
`
|
||||
</div>`
|
||||
}
|
||||
|
||||
@@ -1,99 +1,242 @@
|
||||
import {CoreFetchCmpt} from '../../Fetch.js';
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
import CoreForm from '../../Form/Form.js';
|
||||
import FormValidation from '../../Form/Validation.js';
|
||||
import FormInput from '../../Form/Input.js';
|
||||
|
||||
var _uuid = 0;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoreFetchCmpt
|
||||
CoreFetchCmpt,
|
||||
CoreForm,
|
||||
FormValidation,
|
||||
FormInput
|
||||
},
|
||||
mixins: [
|
||||
Phrasen
|
||||
],
|
||||
emits: [
|
||||
'setInfos',
|
||||
'setStatus'
|
||||
],
|
||||
props: {
|
||||
prestudentId: Number,
|
||||
studierendenantragId: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
data: null
|
||||
data: null,
|
||||
saving: false,
|
||||
formData: {
|
||||
grund: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
statusSeverity() {
|
||||
switch (this.data.status)
|
||||
switch (this.data?.status)
|
||||
{
|
||||
case 'Erstellt': return 'info';
|
||||
case 'Genehmigt': return 'success';
|
||||
default: return 'info';
|
||||
case 'Pause':
|
||||
case 'Zurueckgezogen': return 'danger';
|
||||
case 'EinspruchAbgelehnt':
|
||||
case 'Abgemeldet': return 'success';
|
||||
default: return 'warning';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
return axios.get(
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
'/components/Antrag/Abmeldung/getDetailsForAntrag/' +
|
||||
this.studierendenantragId
|
||||
).then(
|
||||
result => {
|
||||
this.data = result.data.retval;
|
||||
return this.$fhcApi.factory
|
||||
.studstatus.abmeldung.getDetails(this.studierendenantragId, this.prestudentId)
|
||||
.then(result => {
|
||||
this.data = result.data;
|
||||
if (this.data.status) {
|
||||
const msg = (this.data.status == 'Pause' && this.data.status_insertvon == "Studienabbruch") ? Vue.computed(() => {
|
||||
let status = this.$p.t('studierendenantrag/status_stop');
|
||||
return this.$p.t('studierendenantrag', 'status_x', {status});
|
||||
}) : Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp}));
|
||||
this.$emit("setStatus", {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
msg,
|
||||
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;
|
||||
|
||||
this.$refs.form.clearValidation();
|
||||
this.$refs.form.factory
|
||||
.studstatus.abmeldung.create(
|
||||
this.data.studiensemester_kurzbz,
|
||||
this.data.prestudent_id,
|
||||
this.formData.grund
|
||||
)
|
||||
.then(result => {
|
||||
if (result.data === true)
|
||||
document.location += "";
|
||||
|
||||
this.data = result.data;
|
||||
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;
|
||||
})
|
||||
.catch(error => {
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
|
||||
severity: 'danger'
|
||||
});
|
||||
this.saving = false;
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
},
|
||||
appendDropDownText(event) {
|
||||
this.formData.grund = event.target.value
|
||||
? this.$p.t('studierendenantrag', event.target.value)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.uuid = _uuid++;
|
||||
},
|
||||
template: `
|
||||
<div class="studierendenantrag-form-abmeldung">
|
||||
<core-fetch-cmpt :api-function="load">
|
||||
<div class="row">
|
||||
<core-form ref="form" class="row">
|
||||
<div class="col-12">
|
||||
<form-validation></form-validation>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'studiengang')}}</th>
|
||||
<th>{{$p.t('lehre', 'studiengang')}}</th>
|
||||
<td align="right">{{data.bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'organisationsform')}}</th>
|
||||
<th>{{$p.t('lehre', 'organisationsform')}}</th>
|
||||
<td align="right">{{data.orgform_bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<td align="right">{{data.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('person', 'personenkennzeichen')}}</th>
|
||||
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
|
||||
<td align="right">{{data.matrikelnr}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'studienjahr')}}</th>
|
||||
<th>{{$p.t('lehre', 'studienjahr')}}</th>
|
||||
<td align="right">{{data.studienjahr_kurzbz}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'semester')}}</th>
|
||||
<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 class="mb-3">
|
||||
<h5>{{p.t('studierendenantrag', 'antrag_grund')}}:</h5>
|
||||
<pre>{{data.grund}}</pre>
|
||||
|
||||
<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" class="form-select" @change="appendDropDownText">
|
||||
<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>
|
||||
<form-input
|
||||
type="textarea"
|
||||
v-model="formData.grund"
|
||||
name="grund"
|
||||
:id="'studierendenantrag-form-abmeldung-' + uuid + '-grund'"
|
||||
rows="5"
|
||||
:disabled="saving"
|
||||
required
|
||||
>
|
||||
</form-input>
|
||||
</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>
|
||||
</core-form>
|
||||
|
||||
<template v-slot:error="{errorMessage}">
|
||||
<div class="alert alert-danger m-0" role="alert">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</template>
|
||||
</core-fetch-cmpt>
|
||||
</div>
|
||||
`
|
||||
</div>`
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import {CoreFetchCmpt} from '../../Fetch.js';
|
||||
import VueDatepicker from '../../vueDatepicker.js.php';
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
import CoreForm from '../../Form/Form.js';
|
||||
import FormValidation from '../../Form/Validation.js';
|
||||
import FormInput from '../../Form/Input.js';
|
||||
|
||||
var _uuid = 0;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoreFetchCmpt,
|
||||
VueDatepicker
|
||||
CoreForm,
|
||||
FormValidation,
|
||||
FormInput
|
||||
},
|
||||
mixins: [
|
||||
Phrasen
|
||||
],
|
||||
emits: [
|
||||
'setInfos',
|
||||
'setStatus'
|
||||
@@ -24,13 +23,9 @@ export default {
|
||||
return {
|
||||
data: null,
|
||||
saving: false,
|
||||
errors: {
|
||||
grund: [],
|
||||
studiensemester: [],
|
||||
datum_wiedereinstieg: [],
|
||||
default: []
|
||||
},
|
||||
attachment: [],
|
||||
stsem: null,
|
||||
currentWiedereinstieg: '',
|
||||
siteUrl: FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router
|
||||
}
|
||||
@@ -40,24 +35,19 @@ export default {
|
||||
switch (this.data.status)
|
||||
{
|
||||
case 'Erstellt': return 'info';
|
||||
case 'Genehmigt': return 'success';
|
||||
case 'Zurückgezogen': return 'danger';
|
||||
default: return 'info';
|
||||
case 'Pause':
|
||||
case 'Zurueckgezogen':
|
||||
case 'Abgelehnt': return 'danger';
|
||||
case 'Genehmigt':
|
||||
case 'EmailVersandt': return 'success';
|
||||
default: return 'warning';
|
||||
}
|
||||
},
|
||||
loadUrl() {
|
||||
if (this.studierendenantragId)
|
||||
return '/components/Antrag/Unterbrechung/getDetailsForAntrag/'+
|
||||
this.studierendenantragId;
|
||||
return '/components/Antrag/Unterbrechung/getDetailsForNewAntrag/' +
|
||||
this.prestudentId;
|
||||
},
|
||||
datumWsFormatted() {
|
||||
let datumUnformatted = '';
|
||||
|
||||
if (this.studierendenantragId) {
|
||||
if (this.data.datum_wiedereinstieg)
|
||||
datumUnformatted = this.data.datum_wiedereinstieg;
|
||||
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;
|
||||
@@ -66,265 +56,252 @@ export default {
|
||||
return datumUnformatted;
|
||||
let datum = new Date(datumUnformatted);
|
||||
return datum.toLocaleDateString();
|
||||
},
|
||||
semesterOffsets() {
|
||||
if (!this.data || !this.data.studiensemester)
|
||||
return [];
|
||||
return Object.values(this.data.studiensemester)
|
||||
.filter(el => !el.disabled)
|
||||
.map(el => el.studiensemester_kurzbz);
|
||||
},
|
||||
semester() {
|
||||
if (!this.stsem)
|
||||
return '';
|
||||
return this.data.semester + this.semesterOffsets.indexOf(this.stsem);
|
||||
}
|
||||
},
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
return this.$fhcApi.factory
|
||||
.studstatus.unterbrechung.getDetails(this.studierendenantragId, this.prestudentId)
|
||||
.then(
|
||||
result => {
|
||||
this.data = result.data;
|
||||
if (this.data.status) {
|
||||
const msg = (this.data.status == 'Pause' && this.data.status_insertvon == "Studienabbruch") ? Vue.computed(() => {
|
||||
let status = this.$p.t('studierendenantrag/status_stop');
|
||||
return this.$p.t('studierendenantrag', 'status_x', {status});
|
||||
}) : Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp}));
|
||||
this.$emit("setStatus", {
|
||||
msg,
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
);
|
||||
);
|
||||
},
|
||||
createAntrag() {
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_saving')}),
|
||||
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.data.studiensemester[this.stsem].wiedereinstieg);
|
||||
this.$refs.form.clearValidation();
|
||||
this.$refs.form.factory
|
||||
.studstatus.unterbrechung.create(
|
||||
this.stsem !== null && this.data.studiensemester[this.stsem].studiensemester_kurzbz,
|
||||
this.data.prestudent_id,
|
||||
this.data.grund,
|
||||
this.stsem !== null && this.currentWiedereinstieg,
|
||||
this.attachment
|
||||
)
|
||||
.then(result => {
|
||||
if (Number.isInteger(result.data))
|
||||
document.location += "/" + result.data;
|
||||
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_error')}),
|
||||
severity: 'danger'
|
||||
this.data = result.data;
|
||||
if (this.data.status)
|
||||
this.$emit("setStatus", {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp})),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
else
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_created')}),
|
||||
severity: 'info'
|
||||
});
|
||||
}
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_created')})),
|
||||
severity: 'info'
|
||||
});
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
|
||||
severity: 'danger'
|
||||
});
|
||||
this.saving = false;
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
},
|
||||
cancelAntrag() {
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_cancelling')}),
|
||||
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.$refs.form.clearValidation();
|
||||
this.$refs.form.factory
|
||||
.studstatus.unterbrechung.cancel(
|
||||
this.data.studierendenantrag_id
|
||||
)
|
||||
.then(result => {
|
||||
if (Number.isInteger(result.data))
|
||||
document.location = document.location.replace(/unterbrechung\/([0-9]*)\/[0-9]*[\/]?$/, 'unterbrechung/$1') + "/" + result.data;
|
||||
|
||||
this.data = result.data;
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_error')}),
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_cancelled')})),
|
||||
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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
else
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_cancelled')}),
|
||||
severity: 'danger'
|
||||
});
|
||||
}
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
|
||||
severity: 'danger'
|
||||
});
|
||||
this.saving = false;
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.uuid = _uuid++;
|
||||
},
|
||||
template: `
|
||||
<div class="studierendenantrag-form-unterbrechung">
|
||||
<core-fetch-cmpt :api-function="load">
|
||||
<div class="row">
|
||||
<core-form ref="form" class="row">
|
||||
<div class="col-12">
|
||||
<div v-for="error in errors.default" class="alert alert-danger" role="alert" v-html="error">
|
||||
</div>
|
||||
<form-validation></form-validation>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'studiengang')}}</th>
|
||||
<th>{{$p.t('lehre', 'studiengang')}}</th>
|
||||
<td align="right">{{data.bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'organisationsform')}}</th>
|
||||
<th>{{$p.t('lehre', 'organisationsform')}}</th>
|
||||
<td align="right">{{data.orgform_bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<td align="right">{{data.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('person', 'personenkennzeichen')}}</th>
|
||||
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
|
||||
<td align="right">{{data.matrikelnr}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'studienjahr')}}</th>
|
||||
<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>
|
||||
<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>
|
||||
<td align="right" v-else>{{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'"
|
||||
>
|
||||
<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(".")}}
|
||||
<label class="form-label">
|
||||
{{$p.t('lehre', 'studiensemester')}}
|
||||
</label>
|
||||
<div>
|
||||
{{data.studiensemester_kurzbz}}
|
||||
</div>
|
||||
</div>
|
||||
<form-input
|
||||
v-else
|
||||
type="select"
|
||||
v-model="stsem"
|
||||
name="studiensemester"
|
||||
:label="$p.t('lehre', 'studiensemester')"
|
||||
required
|
||||
@input="currentWiedereinstieg = ''"
|
||||
>
|
||||
<option v-for="(stsem, index) in data.studiensemester" :key="index" :value="index" :disabled="stsem.disabled">
|
||||
{{stsem.studiensemester_kurzbz}}
|
||||
</option>
|
||||
</form-input>
|
||||
</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" class="form-control">
|
||||
{{p.t('ui/select_studiensemester')}}
|
||||
</div>
|
||||
<div v-else class="form-control">
|
||||
{{datumWsFormatted}}
|
||||
</div>
|
||||
|
||||
<div v-if="errors.datum_wiedereinstieg.length" class="invalid-feedback d-block">
|
||||
{{errors.datum_wiedereinstieg.join(".")}}
|
||||
<label class="form-label">
|
||||
{{$p.t('studierendenantrag', 'antrag_datum_wiedereinstieg')}}
|
||||
</label>
|
||||
<div>
|
||||
{{datumWsFormatted}}
|
||||
</div>
|
||||
</div>
|
||||
<form-input
|
||||
v-else-if="stsem === null"
|
||||
type="select"
|
||||
:label="$p.t('studierendenantrag', 'antrag_datum_wiedereinstieg')"
|
||||
modelValue=""
|
||||
name="datum_wiedereinstieg"
|
||||
disabled
|
||||
>
|
||||
<template #default>
|
||||
<option value="" selected disabled hidden>{{$p.t('ui/select_studiensemester')}}</option>
|
||||
</template>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-else
|
||||
type="select"
|
||||
:label="$p.t('studierendenantrag', 'antrag_datum_wiedereinstieg')"
|
||||
v-model="currentWiedereinstieg"
|
||||
name="datum_wiedereinstieg"
|
||||
>
|
||||
<option v-for="sem in data.studiensemester[stsem].wiedereinstieg" :key="sem.studiensemester_kurzbz" :value="sem.start" :disabled="sem.disabled">
|
||||
{{sem.studiensemester_kurzbz}}
|
||||
</option>
|
||||
</form-input>
|
||||
</div>
|
||||
<div v-if="data.studierendenantrag_id" 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'"
|
||||
<div class="col-sm-6 mb-3">
|
||||
<form-input
|
||||
v-if="data.studierendenantrag_id"
|
||||
type="textarea"
|
||||
:label="$p.t('studierendenantrag', 'antrag_grund') + ':'"
|
||||
v-model="data.grund"
|
||||
name="grund"
|
||||
rows="5"
|
||||
:disabled="saving"
|
||||
readonly
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-else
|
||||
ref="grund"
|
||||
type="textarea"
|
||||
:label="$p.t('studierendenantrag', 'antrag_grund') + ':'"
|
||||
v-model="data.grund"
|
||||
name="grund"
|
||||
:disabled="saving"
|
||||
rows="5"
|
||||
required
|
||||
></textarea>
|
||||
<div v-if="errors.grund.length" class="invalid-feedback">
|
||||
{{errors.grund.join(".")}}
|
||||
</div>
|
||||
>
|
||||
</form-input>
|
||||
</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">
|
||||
<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>
|
||||
<form-input
|
||||
v-else
|
||||
ref="attachment"
|
||||
type="uploadfile"
|
||||
:label="$p.t('studierendenantrag', 'antrag_dateianhaenge')"
|
||||
v-model="attachment"
|
||||
name="attachment"
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
<div class="col-12 text-end">
|
||||
<button
|
||||
@@ -334,7 +311,7 @@ export default {
|
||||
@click="createAntrag"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{p.t('studierendenantrag', 'btn_create_Unterbrechung')}}
|
||||
{{$p.t('studierendenantrag', 'btn_create_Unterbrechung')}}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="data.status == 'Erstellt'"
|
||||
@@ -343,16 +320,15 @@ export default {
|
||||
@click="cancelAntrag"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{p.t('studierendenantrag', 'btn_cancel')}}
|
||||
{{$p.t('studierendenantrag', 'btn_cancel')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</core-form>
|
||||
<template v-slot:error="{errorMessage}">
|
||||
<div class="alert alert-danger m-0" role="alert">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</template>
|
||||
</core-fetch-cmpt>
|
||||
</div>
|
||||
`
|
||||
</div>`
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import {CoreFetchCmpt} from '../../Fetch.js';
|
||||
import VueDatepicker from '../../vueDatepicker.js.php';
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
import CoreForm from '../../Form/Form.js';
|
||||
import FormValidation from '../../Form/Validation.js';
|
||||
|
||||
var _uuid = 0;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoreFetchCmpt,
|
||||
VueDatepicker
|
||||
CoreForm,
|
||||
FormValidation
|
||||
},
|
||||
mixins: [
|
||||
Phrasen
|
||||
],
|
||||
emits: [
|
||||
'setInfos',
|
||||
'setStatus',
|
||||
@@ -26,12 +23,6 @@ export default {
|
||||
return {
|
||||
data: null,
|
||||
saving: false,
|
||||
errors: {
|
||||
grund: [],
|
||||
default: []
|
||||
},
|
||||
siteUrl: FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router,
|
||||
infos: []
|
||||
}
|
||||
},
|
||||
@@ -39,16 +30,16 @@ export default {
|
||||
statusSeverity() {
|
||||
switch (this.data.status)
|
||||
{
|
||||
case 'Erstellt': return 'info';
|
||||
case 'Offen':
|
||||
case 'Erstellt':
|
||||
case 'ErsteAufforderungVersandt': return 'info';
|
||||
case 'Genehmigt': return 'success';
|
||||
case 'Verzichtet': return 'danger';
|
||||
default: return 'info';
|
||||
case 'Pause':
|
||||
case 'Verzichtet':
|
||||
case 'Abgemeldet': return 'danger';
|
||||
default: return 'warning';
|
||||
}
|
||||
},
|
||||
loadUrl() {
|
||||
return '/components/Antrag/Wiederholung/getDetailsForNewAntrag/' +
|
||||
this.prestudentId;
|
||||
},
|
||||
datumPruefungFormatted() {
|
||||
if(!this.data.pruefungsdatum)
|
||||
return '';
|
||||
@@ -58,25 +49,27 @@ export default {
|
||||
},
|
||||
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;
|
||||
return this.$fhcApi.factory
|
||||
.studstatus.wiederholung.getDetails(
|
||||
this.prestudentId
|
||||
)
|
||||
.then(result => {
|
||||
this.data = result.data;
|
||||
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.data.statustyp = this.$p.t('studierendenantrag', 'status_open');
|
||||
}
|
||||
this.$emit('update:status', this.data.status);
|
||||
const msg = (this.data.status == 'Pause' && this.data.status_insertvon == "Studienabbruch") ? Vue.computed(() => {
|
||||
let status = this.$p.t('studierendenantrag/status_stop');
|
||||
return this.$p.t('studierendenantrag', 'status_x', {status});
|
||||
}) : Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.data.statustyp}));
|
||||
this.$emit("setStatus", {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
msg,
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
return result;
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
createAntrag() {
|
||||
this.createAntragWithStatus(true);
|
||||
@@ -85,98 +78,79 @@ export default {
|
||||
this.createAntragWithStatus(false);
|
||||
},
|
||||
createAntragWithStatus(repeat) {
|
||||
let func = repeat ? 'createAntrag' : 'cancelAntrag';
|
||||
let func = repeat ? 'create' : 'cancel';
|
||||
let nextState = repeat ? 'Erstellt' : 'Verzichtet';
|
||||
|
||||
this.$emit('setStatus', {
|
||||
msg: this.p.t_ref('studierendenantrag', 'status_x', {status: this.p.t('studierendenantrag', 'status_saving')}),
|
||||
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: this.p.t_ref('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: this.p.t_ref('studierendenantrag', 'status_x', {status: this.data.statustyp}),
|
||||
severity: this.statusSeverity
|
||||
});
|
||||
}
|
||||
this.$refs.form.factory
|
||||
.studstatus.wiederholung[func](
|
||||
this.data.prestudent_id,
|
||||
this.data.studiensemester_kurzbz
|
||||
)
|
||||
.then(result => {
|
||||
if (result.data === true)
|
||||
document.location += "";
|
||||
|
||||
this.data = result.data;
|
||||
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;
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$emit('setStatus', {
|
||||
msg: Vue.computed(() => this.$p.t('studierendenantrag', 'status_x', {status: this.$p.t('studierendenantrag', 'status_error')})),
|
||||
severity: 'danger'
|
||||
});
|
||||
this.saving = false;
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.uuid = _uuid++;
|
||||
},
|
||||
mounted() {
|
||||
this.infos = [...Array(5).keys()].map(n => ({
|
||||
body: this.p.t_ref('studierendenantrag', 'infotext_Wiederholung_' + 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">
|
||||
<core-form ref="form" class="row">
|
||||
<div class="col-12">
|
||||
<div v-for="error in errors.default" class="alert alert-danger" role="alert" v-html="error">
|
||||
</div>
|
||||
<form-validation></form-validation>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'studiengang')}}</th>
|
||||
<th>{{$p.t('lehre', 'studiengang')}}</th>
|
||||
<td align="right">{{data.bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('lehre', 'organisationsform')}}</th>
|
||||
<th>{{$p.t('lehre', 'organisationsform')}}</th>
|
||||
<td align="right">{{data.orgform_bezeichnung}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<th>{{$p.t('projektarbeitsbeurteilung', 'nameStudierende')}}</th>
|
||||
<td align="right">{{data.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('person', 'personenkennzeichen')}}</th>
|
||||
<th>{{$p.t('person', 'personenkennzeichen')}}</th>
|
||||
<td align="right">{{data.matrikelnr}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{p.t('studierendenantrag', 'antrag_Wiederholung_pruefung')}}</th>
|
||||
<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>
|
||||
<th>{{$p.t('studierendenantrag', 'antrag_Wiederholung_pruefung_date')}}</th>
|
||||
<td align="right">{{datumPruefungFormatted}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -190,19 +164,19 @@ export default {
|
||||
@click="createAntrag"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{p.t('studierendenantrag/antrag_Wiederholung_button_yes')}}
|
||||
{{$p.t('studierendenantrag/antrag_Wiederholung_button_yes')}}
|
||||
</button>
|
||||
<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>
|
||||
{{$p.t('studierendenantrag/antrag_Wiederholung_button_no')}}
|
||||
</button>-->
|
||||
</div>
|
||||
</div>
|
||||
</core-form>
|
||||
<template v-slot:error="{errorMessage}">
|
||||
<div class="alert alert-danger m-0" role="alert">
|
||||
{{ errorMessage }}
|
||||
|
||||
@@ -5,7 +5,6 @@ import GrundPopup from './Leitung/GrundPopup.js';
|
||||
import LvPopup from './Leitung/LvPopup.js';
|
||||
import BsAlert from '../Bootstrap/Alert.js';
|
||||
import FhcLoader from '../Loader.js';
|
||||
import Phrasen from '../../mixins/Phrasen.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -14,7 +13,6 @@ export default {
|
||||
LeitungActions,
|
||||
FhcLoader
|
||||
},
|
||||
mixins: [Phrasen],
|
||||
props: {
|
||||
stgL: Array,
|
||||
stgA: Array
|
||||
@@ -31,32 +29,28 @@ export default {
|
||||
stgkzL() {
|
||||
if (!this.stgL)
|
||||
return [];
|
||||
return this.stgL.map(stg => stg.studiengang_kz);
|
||||
return this.stgL.map(stg => parseInt(stg));
|
||||
},
|
||||
stgkzA() {
|
||||
if (!this.stgA)
|
||||
return [];
|
||||
return this.stgA.map(stg => stg.studiengang_kz);
|
||||
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);
|
||||
});
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.getStgs()
|
||||
.then(result => this.stgs = result.data)
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
changeFilter(evt) {
|
||||
this.filter = evt.target.value || undefined;
|
||||
changeFilter(filter) {
|
||||
this.filter = filter || undefined;
|
||||
this.reload();
|
||||
},
|
||||
reload() {
|
||||
this.$refs.table.reload(this.filter);
|
||||
if (this.$refs.table)
|
||||
this.$refs.table.reload(this.filter);
|
||||
this.loadFilter();
|
||||
},
|
||||
download() {
|
||||
@@ -79,7 +73,7 @@ export default {
|
||||
{
|
||||
let countAntrage = 0;
|
||||
LvPopup
|
||||
.popup(this.p.t('studierendenantrag','title_show_lvs', currentAntrag), {
|
||||
.popup(this.$p.t('studierendenantrag','title_show_lvs', currentAntrag), {
|
||||
antragId: currentAntrag.studierendenantrag_id,
|
||||
footer: true,
|
||||
dialogClass: 'modal-lg',
|
||||
@@ -104,21 +98,9 @@ export default {
|
||||
}
|
||||
} 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);
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.approve(oks)
|
||||
.then(this.showResults);
|
||||
}
|
||||
},
|
||||
actionReject(evt, gruende) {
|
||||
@@ -128,7 +110,7 @@ export default {
|
||||
var currentAntrag = antraege.pop();
|
||||
if (currentAntrag) {
|
||||
GrundPopup
|
||||
.popup(this.p.t('studierendenantrag', 'title_grund', {id: currentAntrag.studierendenantrag_id}), {
|
||||
.popup(this.$p.t('studierendenantrag', 'title_grund', {id: currentAntrag.studierendenantrag_id}), {
|
||||
countRemaining: antraege.length
|
||||
})
|
||||
.then(result => {
|
||||
@@ -148,61 +130,38 @@ export default {
|
||||
.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);
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.reject(gruende)
|
||||
.then(this.showResults);
|
||||
}
|
||||
},
|
||||
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);
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.reopen(gruende)
|
||||
.then(this.showResults);
|
||||
},
|
||||
actionPause(evt) {
|
||||
var antraege = evt || this.selectedData;
|
||||
this.$refs.loader.show();
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.pause(antraege)
|
||||
.then(this.showResults);
|
||||
},
|
||||
actionUnpause(evt) {
|
||||
var antraege = evt || this.selectedData;
|
||||
this.$refs.loader.show();
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.unpause(antraege)
|
||||
.then(this.showResults);
|
||||
},
|
||||
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);
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.object(antraege)
|
||||
.then(this.showResults);
|
||||
},
|
||||
actionoObjectionDeny(evt, gruende) {
|
||||
var antraege = evt || this.selectedData;
|
||||
@@ -211,7 +170,7 @@ export default {
|
||||
var currentAntrag = antraege.pop();
|
||||
if (currentAntrag) {
|
||||
GrundPopup
|
||||
.popup(this.p.t('studierendenantrag', 'title_grund', {id: currentAntrag.studierendenantrag_id}), {
|
||||
.popup(this.$p.t('studierendenantrag', 'title_grund', {id: currentAntrag.studierendenantrag_id}), {
|
||||
countRemaining : antraege.length,
|
||||
optional: true
|
||||
})
|
||||
@@ -230,84 +189,31 @@ export default {
|
||||
.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);
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.denyObjection(gruende)
|
||||
.then(this.showResults);
|
||||
}
|
||||
},
|
||||
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);
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.approveObjection(antraege)
|
||||
.then(this.showResults);
|
||||
},
|
||||
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);
|
||||
this.$fhcApi.factory
|
||||
.studstatus.abmeldung.cancel(antraege)
|
||||
.then(this.showResults);
|
||||
},
|
||||
showValidation(results) {
|
||||
var errors = results.filter(res => res.data.error);
|
||||
showResults(results) {
|
||||
let fulfilled = results.filter(res => res.status == 'fulfilled');
|
||||
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'});
|
||||
//fulfilled.forEach(a => this.$fhcAlert.alertDefault('success', '#' + a.value.data, 'Approved, ...'));
|
||||
if (fulfilled.length)
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -338,6 +244,7 @@ export default {
|
||||
ref="table"
|
||||
:stg-a="stgkzA"
|
||||
:stg-l="stgkzL"
|
||||
:filter="filter"
|
||||
v-model:columnData="columns"
|
||||
v-model:selectedData="selectedData"
|
||||
@action:approve="actionApprove"
|
||||
@@ -347,6 +254,9 @@ export default {
|
||||
@action:objectionDeny="actionoObjectionDeny"
|
||||
@action:objectionApprove="actionObjectionApprove"
|
||||
@action:cancel="actionCancel"
|
||||
@action:pause="actionPause"
|
||||
@action:unpause="actionUnpause"
|
||||
@reload="reload"
|
||||
>
|
||||
</leitung-table>
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import ActionsNew from './Actions/New.js';
|
||||
import ActionsColumns from './Actions/Columns.js';
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActionsNew,
|
||||
ActionsColumns
|
||||
},
|
||||
mixins: [Phrasen],
|
||||
props: {
|
||||
selectedData: Array,
|
||||
columns: Array,
|
||||
@@ -67,17 +65,17 @@ export default {
|
||||
<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')">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -6,9 +6,6 @@ export default {
|
||||
toggleColumn(col) {
|
||||
col.visible = !col.visible;
|
||||
col.original.toggle()
|
||||
},
|
||||
show() {
|
||||
|
||||
}
|
||||
},
|
||||
template: `
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
import BsAlert from '../../../Bootstrap/Alert.js';
|
||||
import BsModal from '../../../Bootstrap/Modal.js';
|
||||
import Phrasen from '../../../../mixins/Phrasen.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BsModal,
|
||||
AutoComplete: primevue.autocomplete
|
||||
},
|
||||
mixins: [
|
||||
Phrasen
|
||||
],
|
||||
emits: [
|
||||
'reload'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
student: ''
|
||||
student: '',
|
||||
abortController: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
newUrl() {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/lehre/Studierendenantrag/abmeldung/' + this.student.prestudent_id;
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/lehre/Studierendenantrag/abmeldungStgl/' + this.student.prestudent_id;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -32,58 +29,74 @@ export default {
|
||||
class: 'position-absolute top-0 start-0 w-100 h-100'
|
||||
}), {
|
||||
dialogClass: 'modal-fullscreen'
|
||||
}, this.p.t('studierendenantrag', 'antrag_header')).then(() => {
|
||||
}, this.$p.t('studierendenantrag', 'antrag_header')).then(() => {
|
||||
this.$emit('reload');
|
||||
this.student = '';
|
||||
});
|
||||
},
|
||||
loadData(evt) {
|
||||
axios.post(
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
'/components/Antrag/Abmeldung/getStudiengaengeAssistenz/',
|
||||
evt
|
||||
).then(
|
||||
result => {
|
||||
if (result.data.error) {
|
||||
BsAlert.popup(result.data.retval, {dialogClass: 'alert alert-danger'});
|
||||
} else {
|
||||
this.data = result.data.retval;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
);
|
||||
if( evt.query.length < 2 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.abortController instanceof AbortController
|
||||
&& this.abortController.signal.aborted === false)
|
||||
{
|
||||
this.abortController.abort();
|
||||
}
|
||||
this.abortController = new AbortController();
|
||||
|
||||
this.$fhcApi.factory
|
||||
.studstatus.leitung.getPrestudents(evt.query, this.abortController.signal)
|
||||
.then(result => {
|
||||
this.data = result.data;
|
||||
this.abortController = null;
|
||||
})
|
||||
.catch(error => {
|
||||
if (this.abortController instanceof AbortController
|
||||
&& this.abortController.signal.aborted === false)
|
||||
{
|
||||
this.abortController.abort();
|
||||
}
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
});
|
||||
}
|
||||
},
|
||||
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')}}
|
||||
{{$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>
|
||||
<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>
|
||||
<label for="newAntragModalAutoComplete">{{$p.t('person','studentIn')}}</label>
|
||||
<div>
|
||||
<auto-complete
|
||||
class="w-100"
|
||||
v-model="student"
|
||||
:suggestions="data"
|
||||
optionLabel = "name"
|
||||
option-label = "name"
|
||||
@complete="loadData"
|
||||
inputId="newAntragModalAutoComplete"
|
||||
input-id="newAntragModalAutoComplete"
|
||||
dropdown
|
||||
dropdown-mode="current"
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<div :title="slotProps.option.prestudent_id">
|
||||
{{slotProps.option.name}}
|
||||
{{slotProps.option.name}}
|
||||
</div>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div class="text-muted px-3 py-2">
|
||||
{{ $p.t('ui/keineEintraegeGefunden') }}
|
||||
</div>
|
||||
</template>
|
||||
</auto-complete>
|
||||
@@ -94,7 +107,7 @@ export default {
|
||||
class="btn btn-primary"
|
||||
:disabled="!this.student"
|
||||
@click.prevent="openForm">
|
||||
{{p.t('studierendenantrag','btn_create')}}
|
||||
{{$p.t('studierendenantrag','btn_create')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
|
||||
import BsAlert from '../../Bootstrap/Alert.js';
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
BsAlert,
|
||||
Phrasen
|
||||
],
|
||||
props: {
|
||||
placeholder: String,
|
||||
@@ -46,17 +43,17 @@ export 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')}}
|
||||
{{$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>
|
||||
<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>
|
||||
<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>`
|
||||
}
|
||||
|
||||
@@ -1,23 +1,73 @@
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
const LOCAL_STORAGE_ID = 'studierendenantrag_leitung_2023-11-14_header_filter';
|
||||
|
||||
export default {
|
||||
mixins: [Phrasen],
|
||||
props: {
|
||||
stgs: Array
|
||||
},
|
||||
emits: [
|
||||
'input'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
todo_value: '',
|
||||
stg_value: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value() {
|
||||
const a = [];
|
||||
if (this.todo_value)
|
||||
a.push(this.todo_value);
|
||||
if (this.stg_value)
|
||||
a.push(this.stg_value);
|
||||
|
||||
return a.join('/');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(n) {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_ID, n);
|
||||
this.$emit('input', n);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
var values = 'todo';
|
||||
const savedPath = window.localStorage.getItem(LOCAL_STORAGE_ID);
|
||||
if (savedPath !== null) {
|
||||
values = savedPath;
|
||||
}
|
||||
|
||||
values = values.split('/');
|
||||
|
||||
if (values.length) {
|
||||
if (values.length == 1) {
|
||||
if (values[0] == 'todo')
|
||||
values.push('');
|
||||
else
|
||||
values.unshift('');
|
||||
}
|
||||
this.stg_value = values.pop();
|
||||
this.todo_value = values.pop();
|
||||
}
|
||||
},
|
||||
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>
|
||||
<h3 class="h5 col m-0">{{$p.t('studierendenantrag', 'studierendenantraege')}}</h3>
|
||||
<div class="col-auto row row-cols-lg-auto g-3 align-items-center">
|
||||
<div class="col-12">
|
||||
<select class="form-select" v-model="todo_value">
|
||||
<option value="todo">{{$p.t('studierendenantrag', 'filter_todo')}}</option>
|
||||
<option value="">{{$p.t('studierendenantrag', 'filter_all')}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<select v-if="stgs.length > 1" class="form-select" v-model="stg_value">
|
||||
<option value="">{{$p.t('global', 'alle')}}</option>
|
||||
<option v-for="stg in stgs" :key="stg.studiengang_kz" :value="stg.studiengang_kz">
|
||||
{{stg.bezeichnung}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import BsAlert from '../../Bootstrap/Alert.js';
|
||||
import {CoreFetchCmpt} from "../../Fetch.js";
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoreFetchCmpt
|
||||
},
|
||||
mixins: [
|
||||
BsAlert,
|
||||
Phrasen
|
||||
BsAlert
|
||||
],
|
||||
props: {
|
||||
footer: Boolean,
|
||||
@@ -18,6 +16,7 @@ export default {
|
||||
data(){
|
||||
return {
|
||||
lvs: null,
|
||||
repeat_last: false,
|
||||
refresh: true,
|
||||
result: false,
|
||||
check: false
|
||||
@@ -37,18 +36,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
setlvs(param) {
|
||||
if(param.error)
|
||||
{
|
||||
this.$refs.fetchCompt.error = true;
|
||||
this.$refs.fetchCompt.errorMessage = param.retval;
|
||||
this.repeat_last = !!param.repeat_last;
|
||||
if (this.repeat_last) {
|
||||
delete param.repeat_last;
|
||||
}
|
||||
else
|
||||
this.lvs = param.retval;
|
||||
this.lvs = param;
|
||||
},
|
||||
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);
|
||||
return this.$fhcApi.factory
|
||||
.studstatus.wiederholung.getLvs(this.antragId);
|
||||
},
|
||||
submit(result) {
|
||||
this.result = [result, this.check];
|
||||
@@ -67,7 +65,7 @@ export default {
|
||||
options = { default: options };
|
||||
return BsAlert.popup.bind(this)(msg, options);
|
||||
},
|
||||
template: `<bs-modal ref="modalContainer" class="bootstrap-prompt" v-bind="$props">
|
||||
template: `<bs-modal ref="modalContainer" class="bootstrap-prompt" v-bind="$props" dialog-class="modal-lg">
|
||||
<template v-slot:title>
|
||||
<slot></slot>
|
||||
</template>
|
||||
@@ -79,29 +77,29 @@ export default {
|
||||
@data-fetched="setlvs">
|
||||
<template #default>
|
||||
<div v-if="lvzugelassenLength == 0">
|
||||
{{p.t('studierendenantrag','error_no_lvs')}}
|
||||
{{$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>{{ $p.t('studierendenantrag',['title_lv_nicht_zugelassen', 'title_lv_wiederholen'][repeat_last ? 1 : 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">{{$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('lehre','note')}}</th>
|
||||
<th scope="col">
|
||||
{{p.t('global','anmerkung')}}
|
||||
{{$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>
|
||||
<td colspan="5">{{$p.t('studierendenantrag/error_stg_last_semester')}}</td>
|
||||
</tr>
|
||||
<template v-else>
|
||||
<tr v-for="lv in lv_arr">
|
||||
@@ -140,10 +138,10 @@ export default {
|
||||
<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>
|
||||
<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>
|
||||
<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>`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import BsModal from '../../Bootstrap/Modal.js';
|
||||
import {CoreFetchCmpt} from '../../Fetch.js';
|
||||
import LvPopup from './LvPopup.js';
|
||||
import Phrasen from '../../../mixins/Phrasen.js';
|
||||
import { dateFilter } from '../../../tabulator/filters/Dates.js';
|
||||
|
||||
export default {
|
||||
@@ -10,12 +9,12 @@ export default {
|
||||
CoreFetchCmpt,
|
||||
LvPopup
|
||||
},
|
||||
mixins: [Phrasen],
|
||||
props: {
|
||||
selectedData: Array,
|
||||
columnData: Array,
|
||||
stgL: Array,
|
||||
stgA: Array
|
||||
stgA: Array,
|
||||
filter: String
|
||||
},
|
||||
emits: [
|
||||
'update:columnData',
|
||||
@@ -26,13 +25,12 @@ export default {
|
||||
'action:object',
|
||||
'action:objectionDeny',
|
||||
'action:objectionApprove',
|
||||
'action:cancel'
|
||||
'action:cancel',
|
||||
'action:pause',
|
||||
'action:unpause'
|
||||
],
|
||||
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: [],
|
||||
@@ -41,22 +39,46 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
reload(stg) {
|
||||
this.table.replaceData(this.ajaxUrl + (stg || ''));
|
||||
this.table.setData('/' + (stg || ''));
|
||||
},
|
||||
download() {
|
||||
this.table.download("csv", "data.csv");
|
||||
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);
|
||||
});
|
||||
return this.$fhcApi.factory
|
||||
.studstatus.leitung.getHistory(this.lastHistoryClickedId)
|
||||
.then(res => {
|
||||
this.historyData = res.data.sort((a, b) => a.insertamum > b.insertamum);
|
||||
})
|
||||
.catch(this.$fhcApi.handleSystemError);
|
||||
},
|
||||
getHistoryStatus(data, index) {
|
||||
if (data.insertvon == 'Studienabbruch')
|
||||
return this.$p.t('studierendenantrag/status_stop');
|
||||
if (index > 0 && this.historyData[index-1].studierendenantrag_statustyp_kurzbz == 'Pause') {
|
||||
if (index > 1 && this.historyData[index-2].studierendenantrag_statustyp_kurzbz != 'Pause') {
|
||||
// NOTE(chris): this is a AbmeldungStgl Pause right after a manual Pause
|
||||
if (data.studierendenantrag_statustyp_kurzbz == 'Pause')
|
||||
return data.typ;
|
||||
// NOTE(chris): this is a manual Pause resumed
|
||||
else
|
||||
return data.typ + ' (' + this.$p.t('studierendenantrag/status_unpaused') + ')';
|
||||
}
|
||||
// NOTE(chris): a series of pause stati always starts with a manual and alternate afterwards
|
||||
let i = 2;
|
||||
while (index-i > 0 && this.historyData[index-i].studierendenantrag_statustyp_kurzbz == 'Pause') i++;
|
||||
if (data.studierendenantrag_statustyp_kurzbz == 'Pause')
|
||||
i++;
|
||||
return i%2
|
||||
? data.typ
|
||||
: data.typ + ' (' + this.$p.t('studierendenantrag/status_unpaused') + ')';
|
||||
}
|
||||
return data.typ;
|
||||
},
|
||||
showHistoryGrund(grund) {
|
||||
this.$refs.modalGrund.$el.addEventListener(
|
||||
@@ -73,12 +95,13 @@ export default {
|
||||
this.$refs.lvList.show();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
async mounted() {
|
||||
await this.$p.loadCategory(['lehre', 'studierendenantrag', 'person', 'global', 'ui']);
|
||||
function dateFormatter(cell)
|
||||
{
|
||||
let val = cell.getValue();
|
||||
if (!val)
|
||||
return '';
|
||||
return ' ';
|
||||
let date = new Date(val);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
@@ -86,9 +109,10 @@ export default {
|
||||
this.table = new Tabulator(this.$refs.table, {
|
||||
placeholder:"Keine zu bearbeitenden Datensätze",
|
||||
movableColumns: true,
|
||||
height: '50vh',
|
||||
layout: "fitDataStretch", // TODO(chris): wont work when changed
|
||||
ajaxURL: this.ajaxUrl,
|
||||
height: '65vh',
|
||||
layout: "fitDataFill",
|
||||
ajaxURL: '/' + (this.filter || ''),
|
||||
ajaxRequestFunc: this.$fhcApi.factory.studstatus.leitung.getAntraege,
|
||||
persistence: { // NOTE(chris): do not store column titles
|
||||
sort: true, //persist column sorting
|
||||
filter: true, //persist filters
|
||||
@@ -97,7 +121,7 @@ export default {
|
||||
page: true, //persist page
|
||||
columns: ["width", "visible"], //persist columns
|
||||
},
|
||||
persistenceID: 'studierendenantrag_leitung',
|
||||
persistenceID: 'studierendenantrag_leitung_2023-11-14',
|
||||
columns: [{
|
||||
formatter: 'rowSelection',
|
||||
titleFormatter: 'rowSelection',
|
||||
@@ -108,10 +132,11 @@ export default {
|
||||
headerSort: false
|
||||
}, {
|
||||
field: 'studierendenantrag_id',
|
||||
title: '#'
|
||||
title: '#',
|
||||
sorter: 'number'
|
||||
}, {
|
||||
field: 'bezeichnung',
|
||||
title: this.p.t('lehre', 'studiengang'),
|
||||
title: this.$p.t('lehre', 'studiengang'),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: {
|
||||
valuesLookup: true,
|
||||
@@ -120,7 +145,7 @@ export default {
|
||||
}
|
||||
}, {
|
||||
field: 'orgform',
|
||||
title: this.p.t('lehre', 'organisationsform'),
|
||||
title: this.$p.t('lehre', 'organisationsform'),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: {
|
||||
valuesLookup: true,
|
||||
@@ -129,7 +154,7 @@ export default {
|
||||
}
|
||||
}, {
|
||||
field: 'typ',
|
||||
title: this.p.t('studierendenantrag', 'antrag_typ'),
|
||||
title: this.$p.t('studierendenantrag', 'antrag_typ'),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: {
|
||||
valuesLookup: true,
|
||||
@@ -137,90 +162,184 @@ export default {
|
||||
autocomplete: true,
|
||||
},
|
||||
formatter: (cell, formatterParams, onRendered) => {
|
||||
return this.p.t('studierendenantrag','antrag_typ_' + cell.getValue());
|
||||
return this.$p.t('studierendenantrag','antrag_typ_' + cell.getValue());
|
||||
}
|
||||
}, {
|
||||
field: 'statustyp',
|
||||
title: this.p.t('studierendenantrag', 'antrag_status'),
|
||||
title: this.$p.t('studierendenantrag', 'antrag_status'),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: {
|
||||
valuesLookup: true,
|
||||
clearable: true,
|
||||
autocomplete: true,
|
||||
},
|
||||
formatter: (cell, formatterParams, onRendered) => {
|
||||
let data = cell.getData();
|
||||
let status = cell.getValue();
|
||||
if (data.status_insertvon == 'Studienabbruch' && data.status == 'Pause')
|
||||
status = this.$p.t('studierendenantrag/status_stop');
|
||||
let link = document.createElement('a');
|
||||
link.href = "#";
|
||||
link.innerHTML = status;
|
||||
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'),
|
||||
title: this.$p.t('person', 'personenkennzeichen'),
|
||||
headerFilter: 'input'
|
||||
}, {
|
||||
field: 'prestudent_id',
|
||||
title: this.p.t('lehre', 'prestudent'),
|
||||
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'),
|
||||
title: this.$p.t('global', 'name'),
|
||||
mutator: (value, data) => (data.nachname + ' ' + data.vorname).replace(/^\s*(.*)\s*$/, '$1'),
|
||||
headerFilter: 'input'
|
||||
}, {
|
||||
field: 'datum',
|
||||
title: this.p.t('global', 'datum'),
|
||||
title: this.$p.t('global', 'datum'),
|
||||
formatter: dateFormatter,
|
||||
headerFilterFunc: 'dates',
|
||||
headerFilter: dateFilter
|
||||
}, {
|
||||
field: 'datum_wiedereinstieg',
|
||||
title: this.p.t('studierendenantrag', 'antrag_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'),
|
||||
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.innerHTML = this.$p.t('studierendenantrag', 'antrag_grund');
|
||||
link.addEventListener('click', () => {
|
||||
this.$refs.modalGrundPre.innerHTML = val;
|
||||
});
|
||||
|
||||
return val ? link : '';
|
||||
return val ? link : ' ';
|
||||
}
|
||||
}, {
|
||||
field: 'dms_id',
|
||||
title: this.p.t('studierendenantrag', 'antrag_dateianhaenge'),
|
||||
title: this.$p.t('studierendenantrag', 'antrag_dateianhaenge'),
|
||||
formatter: (cell, formatterParams, onRendered) => {
|
||||
let val = cell.getValue();
|
||||
if (!val)
|
||||
return '';
|
||||
return '<a href="' + FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
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 + '" target="_blank"><i class="fa fa-paperclip" aria-hidden="true"></i> ' + this.p.t('studierendenantrag', 'antrag_anhang') + '</a>';
|
||||
'/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 == 'Unterbrechung') && (data.status == 'Genehmigt')) {
|
||||
let allowed_status_for_download = [];
|
||||
switch (data.typ) {
|
||||
case 'Abmeldung':
|
||||
allowed_status_for_download = ['Genehmigt'];
|
||||
break;
|
||||
case 'AbmeldungStgl':
|
||||
allowed_status_for_download = ['Genehmigt', 'Beeinsprucht', 'EinspruchAbgelehnt', 'Abgemeldet'];
|
||||
break;
|
||||
case 'Unterbrechung':
|
||||
allowed_status_for_download = ['Genehmigt', 'EmailVersandt'];
|
||||
break;
|
||||
case 'Wiederholung':
|
||||
allowed_status_for_download = ['Abgemeldet'];
|
||||
break;
|
||||
}
|
||||
if (allowed_status_for_download.includes(data.status)) {
|
||||
// NOTE(chris): Download PDF
|
||||
let button = document.createElement('a');
|
||||
button.innerHTML = '<i class="fa-solid fa-download" title="' + this.p.t('studierendenantrag', 'btn_download_antrag') + '"></i>';
|
||||
// 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 == 'Wiederholung' && (data.status == 'ErsteAufforderungVersandt' || data.status == 'ZweiteAufforderungVersandt')) {
|
||||
// NOTE(chris): Pause
|
||||
let button = document.createElement('button');
|
||||
let icon = document.createElement('i');
|
||||
let span = document.createElement('span');
|
||||
|
||||
icon.className = "fa-solid fa-pause";
|
||||
icon.setAttribute('aria-hidden', true);
|
||||
icon.setAttribute('title', this.$p.t('studierendenantrag', 'btn_pause'));
|
||||
|
||||
span.className = "fa-sr-only";
|
||||
span.append(this.$p.t('studierendenantrag', 'btn_pause'));
|
||||
|
||||
button.append(icon);
|
||||
button.append(span);
|
||||
button.className = "btn btn-outline-secondary";
|
||||
button.addEventListener('click', () => this.$emit('action:pause', [cell.getData()]));
|
||||
container.append(button);
|
||||
}
|
||||
|
||||
let canUnpause = data.status == 'Pause' && !['AbmeldungStgl', 'Studienabbruch'].includes(data.status_insertvon);
|
||||
if (!canUnpause && data.status == 'Pause' && data.status_insertvon == 'AbmeldungStgl') {
|
||||
canUnpause = cell.getTable().getData().filter(row =>
|
||||
row.prestudent_id == data.prestudent_id
|
||||
&& row.typ == 'AbmeldungStgl'
|
||||
&& row.status == 'Zurueckgezogen'
|
||||
&& row.status_insertamum == data.status_insertamum
|
||||
).length;
|
||||
}
|
||||
if (canUnpause) {
|
||||
// NOTE(chris): Unpause
|
||||
let button = document.createElement('button');
|
||||
let icon = document.createElement('i');
|
||||
let span = document.createElement('span');
|
||||
|
||||
icon.className = "fa-solid fa-play";
|
||||
icon.setAttribute('aria-hidden', true);
|
||||
icon.setAttribute('title', this.$p.t('studierendenantrag', 'btn_unpause'));
|
||||
|
||||
span.className = "fa-sr-only";
|
||||
span.append(this.$p.t('studierendenantrag', 'btn_unpause'));
|
||||
|
||||
button.append(icon);
|
||||
button.append(span);
|
||||
button.className = "btn btn-outline-secondary";
|
||||
button.addEventListener('click', () => this.$emit('action:unpause', [cell.getData()]));
|
||||
container.append(button);
|
||||
}
|
||||
|
||||
if (data.typ == 'AbmeldungStgl' && data.status == 'Genehmigt') {
|
||||
// NOTE(chris): Object
|
||||
let button = document.createElement('button');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_object');
|
||||
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);
|
||||
@@ -229,14 +348,14 @@ export default {
|
||||
if (data.typ == 'AbmeldungStgl' && data.status == 'Beeinsprucht') {
|
||||
// NOTE(chris): Deny Objection
|
||||
let button = document.createElement('button');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_objection_deny');
|
||||
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.innerHTML = this.p.t('studierendenantrag', 'btn_objection_approve');
|
||||
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);
|
||||
@@ -246,7 +365,7 @@ export default {
|
||||
// NOTE(chris): Reopen
|
||||
if (data.typ == 'Wiederholung' && data.status == 'Verzichtet') {
|
||||
let button = document.createElement('button');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_reopen');
|
||||
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);
|
||||
@@ -254,7 +373,7 @@ export default {
|
||||
// NOTE(chris): Lv Zuweisen
|
||||
if (data.typ == 'Wiederholung' && (data.status == 'Erstellt' || data.status == 'Lvszugewiesen')) {
|
||||
let button = document.createElement('a');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_lvzuweisen');
|
||||
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 +
|
||||
@@ -267,16 +386,16 @@ export default {
|
||||
class: 'position-absolute top-0 start-0 w-100 h-100'
|
||||
}), {
|
||||
dialogClass: 'modal-fullscreen'
|
||||
}, this.p.t('studierendenantrag', 'title_lvzuweisen', cell.getData())).then(() => {
|
||||
}, 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') {
|
||||
if (data.typ == 'AbmeldungStgl' && (data.status == 'Erstellt' || data.status == 'Genehmigt' )) {
|
||||
let button = document.createElement('button');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_cancel');
|
||||
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);
|
||||
@@ -287,7 +406,7 @@ export default {
|
||||
// NOTE(chris): Approve
|
||||
if ((data.typ == 'Wiederholung' && data.status == 'Lvszugewiesen') || (data.typ != 'Wiederholung' && data.status == 'Erstellt')) {
|
||||
let button = document.createElement('button');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_approve');
|
||||
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);
|
||||
@@ -295,7 +414,7 @@ export default {
|
||||
// NOTE(chris): Reject (Unterbrechung braucht grund)
|
||||
if (data.status == 'Erstellt' && data.typ == 'Unterbrechung') {
|
||||
let button = document.createElement('button');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_reject');
|
||||
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);
|
||||
@@ -305,16 +424,14 @@ export default {
|
||||
// NOTE(chris): Show LVs
|
||||
if (data.typ == 'Wiederholung' && (data.status == 'Lvszugewiesen' || data.status == 'Genehmigt')) {
|
||||
let button = document.createElement('button');
|
||||
button.innerHTML = this.p.t('studierendenantrag', 'btn_show_lvs');
|
||||
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);
|
||||
}
|
||||
|
||||
// TODO(chris): not yet perfect
|
||||
onRendered(() => {
|
||||
cell.getColumn().setWidth(true);
|
||||
});
|
||||
if (container.innerHTML == '')
|
||||
container.innerHTML = ' ';
|
||||
|
||||
return container;
|
||||
}
|
||||
@@ -325,7 +442,7 @@ export default {
|
||||
let columnData = [];
|
||||
for (let col of columns) {
|
||||
let def = col.getDefinition();
|
||||
if (def.title) {
|
||||
if (def.title && !def.frozen) {
|
||||
columnData.push({
|
||||
title: def.title,
|
||||
visible: col.isVisible(),
|
||||
@@ -338,40 +455,33 @@ export default {
|
||||
this.table.on("rowSelectionChanged", data => {
|
||||
this.$emit('update:selectedData', data);
|
||||
});
|
||||
this.table.on("cellClick", (e, cell) => {
|
||||
if (cell.getColumn().getField() == 'statustyp') {
|
||||
this.lastHistoryClickedId = cell.getData().studierendenantrag_id;
|
||||
this.$refs.historyLoader.fetchData();
|
||||
this.$refs.history.show();
|
||||
}
|
||||
});
|
||||
},
|
||||
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>
|
||||
<pre ref="modalGrundPre"></pre>
|
||||
<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>
|
||||
<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">
|
||||
<tr v-for="(status, index) in historyData" :key="status.studierendenantrag_status_id">
|
||||
<td>{{(new Date(status.insertamum)).toLocaleString()}}</td>
|
||||
<td>{{status.insertvon}}</td>
|
||||
<td>{{status.typ}}</td>
|
||||
<td>{{getHistoryStatus(status, index)}}</td>
|
||||
<td>
|
||||
<a v-if="status.grund" href="#modal-grund" data-bs-toggle="modal" @click="showHistoryGrund(status.grund)">
|
||||
{{p.t('studierendenantrag', 'antrag_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" dialog-class="modal-lg">
|
||||
{{p.t('studierendenantrag', 'title_show_lvs', lvsData ? lvsData : {name: ''}) }}
|
||||
<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>
|
||||
`
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import StudierendenantragStatus from './Status.js';
|
||||
import Phrasen from '../../mixins/Phrasen.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StudierendenantragStatus
|
||||
},
|
||||
mixins: [Phrasen],
|
||||
props: {
|
||||
antragId: Number,
|
||||
initialStatusCode: String,
|
||||
@@ -43,10 +41,11 @@ export default {
|
||||
methods: {
|
||||
save() {
|
||||
this.isloading = true;
|
||||
const forbiddenLvs = this.lvs1.filter(lv => lv.antrag_zugelassen && !lv._children).map(lv => ({
|
||||
const forbiddenLvs = this.lvs1.filter(lv => (lv.antrag_zugelassen || this.lvs.repeat_last)
|
||||
&& !lv._children).map(lv => ({
|
||||
studierendenantrag_id: this.antragId,
|
||||
lehrveranstaltung_id: lv.lehrveranstaltung_id,
|
||||
zugelassen: 0,
|
||||
zugelassen: this.lvs.repeat_last ? (lv.antrag_zugelassen ? 1 : 2) : 0,
|
||||
anmerkung: lv.antrag_anmerkung || "",
|
||||
studiensemester_kurzbz: this.lvs1sem
|
||||
}));
|
||||
@@ -57,39 +56,19 @@ export default {
|
||||
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})
|
||||
this.$fhcApi.factory
|
||||
.studstatus.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, 'alert-danger');
|
||||
this.statusCode = 0;
|
||||
this.statusMsg = 'Error';
|
||||
}
|
||||
this.$fhcAlert.alertSuccess('Speichern erfolgreich');
|
||||
this.statusCode = response.data[0].studierendenantrag_statustyp_kurzbz;
|
||||
this.statusMsg = response.data[0].typ;
|
||||
this.isloading = false;
|
||||
}).catch(error => {
|
||||
this.addAlert(error.message, 'alert-danger');
|
||||
})
|
||||
.catch(error => {
|
||||
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() {
|
||||
@@ -97,57 +76,59 @@ export default {
|
||||
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 = {};
|
||||
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);
|
||||
this.$p
|
||||
.loadCategory(['ui', 'lehre', 'studierendenantrag', 'global'])
|
||||
.then(() => this.antragId)
|
||||
.then(this.$fhcApi.factory.studstatus.wiederholung.getLvs)
|
||||
.then(result => {
|
||||
let res = {};
|
||||
for (var k in result.data) {
|
||||
if (k === 'repeat_last')
|
||||
continue;
|
||||
if (result.data[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[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);
|
||||
}
|
||||
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);
|
||||
var table = new Tabulator(this.$refs["lvtable" + k.substr(0,1)], {
|
||||
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) => {
|
||||
}
|
||||
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 && !result.data.repeat_last ? 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)
|
||||
if (data._children || !data.zeugnis)
|
||||
return "";
|
||||
let input = document.createElement('input');
|
||||
input.className = "form-check-input";
|
||||
@@ -167,84 +148,86 @@ export default {
|
||||
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();
|
||||
}
|
||||
},
|
||||
{
|
||||
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;
|
||||
});
|
||||
|
||||
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 "";
|
||||
}
|
||||
return input;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
this.lvs = result.data.retval;
|
||||
}
|
||||
]
|
||||
};
|
||||
var table = new Tabulator(this.$refs["lvtable" + k.substr(0,1)], options);
|
||||
}
|
||||
}
|
||||
);
|
||||
this.lvs = result.data;
|
||||
})
|
||||
.catch(error => {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
this.isloading = true;
|
||||
});
|
||||
},
|
||||
template: `
|
||||
<div class="col-sm-8">
|
||||
<div ref="alertbox"></div>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<span class="d-flex justify-content-between h4">
|
||||
<span>{{p.t('studierendenantrag', 'title_lv_nicht_zugelassen')}}</span>
|
||||
<span>{{lvs.repeat_last ? $p.t('studierendenantrag', 'title_lv_wiederholen') : $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>{{$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>
|
||||
<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-4">
|
||||
<div class="col-sm-2">
|
||||
<studierendenantrag-status :msg="statusMsg" :severity="statusSeverity"></studierendenantrag-status>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import accessibility from "../directives/accessibility.js";
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
accessibility
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'changed'
|
||||
],
|
||||
props: {
|
||||
config: {
|
||||
type: [String, Array, Object, Promise],
|
||||
required: true
|
||||
},
|
||||
default: String,
|
||||
modelValue: [String, Number, Boolean, Array, Object, Date, Function, Symbol],
|
||||
vertical: Boolean,
|
||||
border: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
current: null,
|
||||
tabs: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentTab() {
|
||||
if (this.tabs[this.current])
|
||||
return this.tabs[this.current];
|
||||
|
||||
return { component: 'div' };
|
||||
},
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(v) {
|
||||
this.$emit('update:modelValue', v);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config(n) {
|
||||
this.initConfig(n);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(key) {
|
||||
this.$emit("change", key)
|
||||
this.current = key;
|
||||
this.$nextTick(() => this.$emit("changed", key));
|
||||
},
|
||||
initConfig(config) {
|
||||
if (!config)
|
||||
return;
|
||||
if (config instanceof Promise)
|
||||
return config
|
||||
.then(result => result.data)
|
||||
.then(this.initConfig)
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
if (typeof config === 'string' || config instanceof String)
|
||||
return this.$fhcApi
|
||||
.get(config)
|
||||
.then(result => result.data)
|
||||
.then(this.initConfig)
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
const tabs = {};
|
||||
|
||||
function _addToTabs(key, item) {
|
||||
if (!item.component)
|
||||
return console.error('Component missing for ' + key);
|
||||
|
||||
tabs[key] = {
|
||||
component: Vue.markRaw(Vue.defineAsyncComponent(() => import(item.component))),
|
||||
title: item.title || key,
|
||||
config: item.config,
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(config))
|
||||
config.forEach((item, key) => _addToTabs(key, item));
|
||||
else
|
||||
Object.entries(config).forEach(([key, item]) => _addToTabs(key, item));
|
||||
|
||||
if (this.current === null || !tabs[this.current]) {
|
||||
if (tabs[this.default])
|
||||
this.current = this.default;
|
||||
else
|
||||
this.current = Object.keys(tabs)[0];
|
||||
}
|
||||
this.tabs = tabs;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initConfig(this.config);
|
||||
},
|
||||
template: `
|
||||
<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"
|
||||
class="nav-item nav-link"
|
||||
:class="{active: tab.key == current}"
|
||||
@click="change(tab.key)"
|
||||
:aria-current="tab.key == current ? 'page' : ''"
|
||||
v-accessibility:tab.[vertical]
|
||||
>
|
||||
{{tab.title}}
|
||||
</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>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>`
|
||||
};
|
||||
@@ -21,7 +21,7 @@ import {CoreRESTClient} from '../../RESTClient.js';
|
||||
const CORE_FILTER_CMPT_TIMEOUT = 7000;
|
||||
|
||||
/**
|
||||
*
|
||||
* TODO(chris): deprecated
|
||||
*/
|
||||
export const CoreFilterAPIs = {
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {CoreFilterAPIs} from './API.js';
|
||||
import {CoreRESTClient} from '../../RESTClient.js';
|
||||
import {CoreFetchCmpt} from '../../components/Fetch.js';
|
||||
import FilterConfig from './Filter/Config.js';
|
||||
import FilterColumns from './Filter/Columns.js';
|
||||
import TableDownload from './Table/Download.js';
|
||||
|
||||
//
|
||||
const FILTER_COMPONENT_NEW_FILTER = 'Filter Component New Filter';
|
||||
@@ -29,11 +30,18 @@ var _uuid = 0;
|
||||
*
|
||||
*/
|
||||
export const CoreFilterCmpt = {
|
||||
emits: ['nwNewEntry'],
|
||||
components: {
|
||||
CoreFetchCmpt
|
||||
CoreFetchCmpt,
|
||||
FilterConfig,
|
||||
FilterColumns,
|
||||
TableDownload
|
||||
},
|
||||
emits: [
|
||||
'nwNewEntry',
|
||||
'click:new'
|
||||
],
|
||||
props: {
|
||||
onNwNewEntry: Function, // NOTE(chris): Hack to get the nwNewEntry listener into $props
|
||||
title: String,
|
||||
sideMenu: {
|
||||
type: Boolean,
|
||||
@@ -45,7 +53,16 @@ export const CoreFilterCmpt = {
|
||||
},
|
||||
tabulatorOptions: Object,
|
||||
tabulatorEvents: Array,
|
||||
tableOnly: Boolean
|
||||
tableOnly: Boolean,
|
||||
reload: Boolean,
|
||||
download: {
|
||||
type: [Boolean, String, Function, Array, Object],
|
||||
default: false
|
||||
},
|
||||
newBtnShow: Boolean,
|
||||
newBtnClass: [String, Array, Object],
|
||||
newBtnDisabled: Boolean,
|
||||
newBtnLabel: String
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
@@ -60,6 +77,7 @@ export const CoreFilterCmpt = {
|
||||
filterFields: null,
|
||||
|
||||
availableFilters: null,
|
||||
selectedFilter: null,
|
||||
|
||||
// FetchCmpt binded properties
|
||||
fetchCmptRefresh: false,
|
||||
@@ -68,7 +86,9 @@ export const CoreFilterCmpt = {
|
||||
fetchCmptDataFetched: null,
|
||||
|
||||
tabulator: null,
|
||||
tableBuilt: false
|
||||
tableBuilt: false,
|
||||
tabulatorHasSelector: false,
|
||||
selectedData: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -114,7 +134,9 @@ export const CoreFilterCmpt = {
|
||||
for (let col of columns)
|
||||
{
|
||||
// If the column has to be displayed or not
|
||||
col.visible = selectedFields.indexOf(col.field) >= 0;
|
||||
/* fields.indexOf(col.field) == -1; ensures displaying formatter colums
|
||||
e.g. column with rowSelection checkboxes or with custom formatted action buttons */
|
||||
col.visible = selectedFields.indexOf(col.field) >= 0 || fields.indexOf(col.field) == -1;
|
||||
|
||||
if (col.hasOwnProperty('resizable'))
|
||||
col.resizable = col.visible;
|
||||
@@ -123,6 +145,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 {};
|
||||
@@ -135,28 +165,41 @@ export const CoreFilterCmpt = {
|
||||
if (!this.uuid)
|
||||
return '';
|
||||
return '-' + this.uuid;
|
||||
},
|
||||
columnsForFilter() {
|
||||
if (!this.filteredColumns || !this.datasetMetadata)
|
||||
return [];
|
||||
const filterTitles = this.filteredColumns.reduce((a,c) => {
|
||||
a[c.field] = c.title;
|
||||
return a;
|
||||
}, {});
|
||||
return this.datasetMetadata.map(el => ({...el, ...{title: filterTitles[el.name]}}));
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if (!this.tableOnly == !this.filterType)
|
||||
alert('You can not have a filter-type in table-only mode!');
|
||||
},
|
||||
created() {
|
||||
this.uuid = _uuid++;
|
||||
if (!this.tableOnly)
|
||||
this.getFilter(); // get the filter data
|
||||
},
|
||||
mounted() {
|
||||
this.initTabulator();
|
||||
},
|
||||
methods: {
|
||||
initTabulator() {
|
||||
reloadTable() {
|
||||
if (this.tableOnly)
|
||||
this.tabulator.setData();
|
||||
else
|
||||
this.getFilter();
|
||||
},
|
||||
async initTabulator() {
|
||||
let placeholder = '< Phrasen Plugin not loaded! >';
|
||||
if (this.$p) {
|
||||
await this.$p.loadCategory('ui');
|
||||
placeholder = this.$p.t('ui/keineDatenVorhanden');
|
||||
}
|
||||
// Define a default tabulator options in case it was not provided
|
||||
let tabulatorOptions = {...{
|
||||
height: 500,
|
||||
layout: "fitColumns",
|
||||
layout: "fitDataStretch",
|
||||
movableColumns: true,
|
||||
reactiveData: true
|
||||
columnDefaults:{
|
||||
tooltip: true,
|
||||
},
|
||||
placeholder,
|
||||
reactiveData: true,
|
||||
persistence: true
|
||||
}, ...(this.tabulatorOptions || {})};
|
||||
|
||||
if (!this.tableOnly) {
|
||||
@@ -164,6 +207,9 @@ export const CoreFilterCmpt = {
|
||||
tabulatorOptions.columns = this.filteredColumns;
|
||||
}
|
||||
|
||||
if (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length)
|
||||
this.tabulatorHasSelector = true;
|
||||
|
||||
// Start the tabulator with the build options
|
||||
this.tabulator = new Tabulator(
|
||||
this.$refs.table,
|
||||
@@ -177,6 +223,9 @@ export const CoreFilterCmpt = {
|
||||
this.tabulator.on(evt.event, evt.handler);
|
||||
}
|
||||
this.tabulator.on('tableBuilt', () => this.tableBuilt = true);
|
||||
this.tabulator.on("rowSelectionChanged", data => {
|
||||
this.selectedData = data;
|
||||
});
|
||||
if (this.tableOnly) {
|
||||
this.tabulator.on('tableBuilt', () => {
|
||||
const cols = this.tabulator.getColumns();
|
||||
@@ -194,67 +243,69 @@ export const CoreFilterCmpt = {
|
||||
}
|
||||
},
|
||||
_updateTabulator() {
|
||||
this.tabulator.setData(this.filteredData);
|
||||
this.tabulatorHasSelector = this.filteredColumns.filter(el => el.formatter == 'rowSelection').length;
|
||||
this.tabulator.setColumns(this.filteredColumns);
|
||||
this.tabulator.setData(this.filteredData);
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
getFilter: function() {
|
||||
//
|
||||
this.startFetchCmpt(CoreFilterAPIs.getFilter, null, this.render);
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
render: function(response) {
|
||||
|
||||
if (CoreRESTClient.hasData(response))
|
||||
{
|
||||
let data = CoreRESTClient.getData(response);
|
||||
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++)
|
||||
{
|
||||
for (let j = 0; j < data.filters.length; j++)
|
||||
getFilter() {
|
||||
if (this.selectedFilter === null)
|
||||
this.startFetchCmpt(this.$fhcApi.factory.filter.getFilter, null, this.render);
|
||||
else
|
||||
this.startFetchCmpt(
|
||||
this.$fhcApi.factory.filter.getFilterById,
|
||||
{
|
||||
if (data.datasetMetadata[i].name == data.filters[j].name)
|
||||
{
|
||||
let filter = data.filters[j];
|
||||
filter.type = data.datasetMetadata[i].type;
|
||||
filterId: this.selectedFilter
|
||||
},
|
||||
this.render
|
||||
);
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
render(response) {
|
||||
let data = response;
|
||||
this.filterName = data.filterName;
|
||||
this.dataset = data.dataset;
|
||||
this.datasetMetadata = data.datasetMetadata;
|
||||
|
||||
this.filterFields.push(filter);
|
||||
break;
|
||||
}
|
||||
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++)
|
||||
{
|
||||
for (let j = 0; j < data.filters.length; j++)
|
||||
{
|
||||
if (data.datasetMetadata[i].name == data.filters[j].name)
|
||||
{
|
||||
let filter = data.filters[j];
|
||||
filter.type = data.datasetMetadata[i].type;
|
||||
|
||||
this.filterFields.push(filter);
|
||||
//break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the side menu is active
|
||||
if (this.sideMenu === true)
|
||||
{
|
||||
this.setSideMenu(data);
|
||||
}
|
||||
else // otherwise use the dropdown in the filter options
|
||||
{
|
||||
this.setDropDownMenu(data);
|
||||
}
|
||||
this.updateTabulator();
|
||||
}
|
||||
else
|
||||
// If the side menu is active
|
||||
if (this.sideMenu === true)
|
||||
{
|
||||
console.error(CoreRESTClient.getError(response));
|
||||
this.setSideMenu(data);
|
||||
}
|
||||
else // otherwise use the dropdown in the filter options
|
||||
{
|
||||
this.setDropDownMenu(data);
|
||||
}
|
||||
this.updateTabulator();
|
||||
},
|
||||
/**
|
||||
* Set the menu
|
||||
*/
|
||||
setSideMenu: function(data) {
|
||||
setSideMenu(data) {
|
||||
let filters = data.sideMenu.filters;
|
||||
let personalFilters = data.sideMenu.personalFilters;
|
||||
let filtersArray = [];
|
||||
@@ -266,6 +317,7 @@ export const CoreFilterCmpt = {
|
||||
if (link == null) link = '#';
|
||||
|
||||
filtersArray[filtersArray.length] = {
|
||||
id: filters[filtersCount].filter_id,
|
||||
link: link + filters[filtersCount].filter_id,
|
||||
description: filters[filtersCount].desc,
|
||||
sort: filtersCount,
|
||||
@@ -280,6 +332,7 @@ export const CoreFilterCmpt = {
|
||||
if (link == null) link = '#';
|
||||
|
||||
filtersArray[filtersArray.length] = {
|
||||
id: personalFilters[filtersCount].filter_id,
|
||||
link: link + personalFilters[filtersCount].filter_id,
|
||||
description: personalFilters[filtersCount].desc,
|
||||
subscriptDescription: personalFilters[filtersCount].subscriptDescription,
|
||||
@@ -306,7 +359,7 @@ export const CoreFilterCmpt = {
|
||||
/**
|
||||
* Set the drop down menu
|
||||
*/
|
||||
setDropDownMenu: function(data) {
|
||||
setDropDownMenu(data) {
|
||||
let filters = data.sideMenu.filters;
|
||||
let personalFilters = data.sideMenu.personalFilters;
|
||||
let filtersArray = [];
|
||||
@@ -318,6 +371,7 @@ export const CoreFilterCmpt = {
|
||||
if (link == null) link = '#';
|
||||
|
||||
filtersArray[filtersArray.length] = {
|
||||
id: filters[filtersCount].filter_id,
|
||||
option: filters[filtersCount].filter_id,
|
||||
description: filters[filtersCount].desc
|
||||
};
|
||||
@@ -330,6 +384,7 @@ export const CoreFilterCmpt = {
|
||||
if (link == null) link = '#';
|
||||
|
||||
filtersArray[filtersArray.length] = {
|
||||
id: personalFilters[filtersCount].filter_id,
|
||||
option: personalFilters[filtersCount].filter_id,
|
||||
description: personalFilters[filtersCount].desc
|
||||
};
|
||||
@@ -340,7 +395,7 @@ export const CoreFilterCmpt = {
|
||||
/**
|
||||
* Used to start/refresh the FetchCmpt
|
||||
*/
|
||||
startFetchCmpt: function(apiFunction, apiFunctionParameters, dataFetchedCallback) {
|
||||
startFetchCmpt(apiFunction, apiFunctionParameters, dataFetchedCallback) {
|
||||
// Assign the function api of the FetchCmpt binded property
|
||||
this.fetchCmptApiFunction = apiFunction;
|
||||
|
||||
@@ -366,12 +421,13 @@ export const CoreFilterCmpt = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlerSaveCustomFilter: function(event) {
|
||||
handlerSaveCustomFilter(customFilterName) {
|
||||
this.selectedFilter = null;
|
||||
//
|
||||
this.startFetchCmpt(
|
||||
CoreFilterAPIs.saveCustomFilter,
|
||||
this.$fhcApi.factory.filter.saveCustomFilter,
|
||||
{
|
||||
customFilterName: this.$refscustomFilterName.value
|
||||
customFilterName
|
||||
},
|
||||
this.getFilter
|
||||
);
|
||||
@@ -379,160 +435,23 @@ export const CoreFilterCmpt = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlerRemoveCustomFilter: function(event) {
|
||||
handlerRemoveCustomFilter(event) {
|
||||
let filterId = event.currentTarget.getAttribute("href").substring(1);
|
||||
if (filterId === this.selectedFilter)
|
||||
this.selectedFilter = null;
|
||||
//
|
||||
this.startFetchCmpt(
|
||||
CoreFilterAPIs.removeCustomFilter,
|
||||
this.$fhcApi.factory.filter.removeCustomFilter,
|
||||
{
|
||||
filterId: event.currentTarget.getAttribute("href").substring(1)
|
||||
filterId: filterId
|
||||
},
|
||||
this.getFilter
|
||||
);
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlerApplyFilterFields: function(event) {
|
||||
let filterFields = [];
|
||||
let filterFieldDivRows = document.getElementById('filterFields').getElementsByClassName('row');
|
||||
|
||||
for (let i = 0; i< filterFieldDivRows.length; i++)
|
||||
{
|
||||
let filterField = {};
|
||||
|
||||
for (let j = 0; j< filterFieldDivRows[i].children.length; j++)
|
||||
{
|
||||
let filterColumn = filterFieldDivRows[i].children[j];
|
||||
let filterColumnElement = filterColumn.children[0];
|
||||
|
||||
// If the first column then search for the fields dropdown
|
||||
if (j == 0) filterColumnElement = filterColumnElement.querySelector('select[name=fieldName]');
|
||||
|
||||
// If the filter name is _not_ null and it is _not_ a new filter
|
||||
if (filterColumnElement.name != null && filterColumnElement.name != FILTER_COMPONENT_NEW_FILTER)
|
||||
{
|
||||
// Condition
|
||||
if (filterColumnElement.name == 'condition' && filterColumnElement.value == "")
|
||||
{
|
||||
alert("Please fill all the filter options");
|
||||
return;
|
||||
}
|
||||
|
||||
// Name
|
||||
if (filterColumnElement.name == 'fieldName')
|
||||
{
|
||||
filterField.name = filterColumnElement.value;
|
||||
}
|
||||
// Operation
|
||||
if (filterColumnElement.name == 'operation')
|
||||
{
|
||||
filterField.operation = filterColumnElement.value;
|
||||
}
|
||||
// Condition
|
||||
if (filterColumnElement.name == 'condition')
|
||||
{
|
||||
filterField.condition = filterColumnElement.value;
|
||||
}
|
||||
// Option
|
||||
if (filterColumnElement.name == 'option')
|
||||
{
|
||||
filterField.option = filterColumnElement.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.entries(filterField).length > 0) filterFields.push(filterField);
|
||||
}
|
||||
|
||||
//
|
||||
this.startFetchCmpt(
|
||||
CoreFilterAPIs.applyFilterFields,
|
||||
{
|
||||
filterFields: filterFields
|
||||
},
|
||||
this.getFilter
|
||||
);
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlerChangeFilterField: function(oldValue, newValue) {
|
||||
|
||||
// If an old filter has been changed
|
||||
if (oldValue != "")
|
||||
{
|
||||
for (let i = 0; i < this.filterFields.length; i++)
|
||||
{
|
||||
if (this.filterFields[i].name == oldValue)
|
||||
{
|
||||
this.filterFields.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then add the new filter
|
||||
for (let i = 0; i < this.datasetMetadata.length; i++)
|
||||
{
|
||||
if (this.datasetMetadata[i].name == newValue)
|
||||
{
|
||||
let filter = {
|
||||
name: this.datasetMetadata[i].name,
|
||||
type: this.datasetMetadata[i].type
|
||||
};
|
||||
|
||||
this.filterFields.push(filter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlerAddNewFilter: function(event) {
|
||||
// Adds a new empty filter
|
||||
this.filterFields.push({
|
||||
name: FILTER_COMPONENT_NEW_FILTER,
|
||||
type: FILTER_COMPONENT_NEW_FILTER_TYPE
|
||||
});
|
||||
},
|
||||
/*
|
||||
*
|
||||
*/
|
||||
handlerToggleSelectedField(field) {
|
||||
|
||||
// If it is a selected field
|
||||
if (this.selectedFields.indexOf(field) != -1)
|
||||
{
|
||||
// then hide it
|
||||
this.tabulator.hideColumn(field);
|
||||
// and remove it from the this.selectedFields property
|
||||
this.selectedFields.splice(this.selectedFields.indexOf(field), 1);
|
||||
}
|
||||
else // otherwise
|
||||
{
|
||||
// show it
|
||||
this.tabulator.showColumn(field);
|
||||
// and add it to the this.selectedFields property
|
||||
this.selectedFields.push(field);
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlerRemoveFilterField: function(event) {
|
||||
//
|
||||
this.startFetchCmpt(
|
||||
CoreFilterAPIs.removeFilterField,
|
||||
{
|
||||
filterField: event.currentTarget.getAttribute('field-to-remove')
|
||||
},
|
||||
this.getFilter
|
||||
);
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handlerGetFilterById: function(event) {
|
||||
|
||||
let filterId = null;
|
||||
@@ -550,16 +469,37 @@ export const CoreFilterCmpt = {
|
||||
filterId = attr.substring(1);
|
||||
}
|
||||
|
||||
// Ajax call
|
||||
this.switchFilter(filterId);
|
||||
},
|
||||
switchFilter(filterId) {
|
||||
this.selectedFilter = filterId;
|
||||
this.getFilter();
|
||||
},
|
||||
applyFilterConfig(filterFields) {
|
||||
this.selectedFilter = null;
|
||||
this.startFetchCmpt(
|
||||
CoreFilterAPIs.getFilterById,
|
||||
this.$fhcApi.factory.filter.applyFilterFields,
|
||||
{
|
||||
filterId: filterId
|
||||
filterFields
|
||||
},
|
||||
this.render
|
||||
this.getFilter
|
||||
);
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if (!this.tableOnly == !this.filterType)
|
||||
alert('You can not have a filter-type in table-only mode!');
|
||||
},
|
||||
created() {
|
||||
if (this.sideMenu && (!this.$props.onNwNewEntry || !(this.$props.onNwNewEntry instanceof Function)))
|
||||
alert('"nwNewEntry" listener is mandatory when sideMenu is true');
|
||||
this.uuid = _uuid++;
|
||||
if (!this.tableOnly)
|
||||
this.getFilter(); // get the filter data
|
||||
},
|
||||
mounted() {
|
||||
this.initTabulator();
|
||||
},
|
||||
template: `
|
||||
<!-- Load filter data -->
|
||||
<core-fetch-cmpt
|
||||
@@ -580,178 +520,53 @@ export const CoreFilterCmpt = {
|
||||
|
||||
<div :id="'filterCollapsables' + idExtra">
|
||||
|
||||
<div class="filter-header-title">
|
||||
<span v-if="!tableOnly" class="filter-header-title-span-filter">[ {{ filterName }} ]</span>
|
||||
<span v-if="!tableOnly" data-bs-toggle="collapse" :data-bs-target="'#collapseFilters' + idExtra" class="filter-header-title-span-icon fa-solid fa-filter fa-xl"></span>
|
||||
<span data-bs-toggle="collapse" :data-bs-target="'#collapseColumns' + idExtra" class="filter-header-title-span-icon fa-solid fa-table-columns fa-xl"></span>
|
||||
</div>
|
||||
|
||||
<div :id="'collapseColumns' + idExtra" class="card-body collapse" :data-bs-parent="'#filterCollapsables' + idExtra">
|
||||
<div class="card">
|
||||
<!-- Filter fields options -->
|
||||
<div class="row card-body filter-options-div">
|
||||
<div class="filter-fields-area">
|
||||
<template v-for="fieldToDisplay in fields">
|
||||
<div
|
||||
class="filter-fields-field"
|
||||
v-bind:class="selectedFields.indexOf(fieldToDisplay) != -1 ? 'text-light bg-dark' : '' "
|
||||
@click="handlerToggleSelectedField(fieldToDisplay)"
|
||||
>
|
||||
{{ fieldNames[fieldToDisplay] || fieldToDisplay }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row justify-content-between flex-wrap">
|
||||
<div v-if="newBtnShow || reload || $slots.actions" class="d-flex gap-2 align-items-baseline flex-wrap">
|
||||
<button v-if="newBtnShow" class="btn btn-primary" :class="newBtnClass" :title="newBtnLabel ? undefined : 'New'" :aria-label="newBtnLabel ? undefined : 'New'" @click="$emit('click:new', $event)" :disabled="newBtnDisabled">
|
||||
<span class="fa-solid fa-plus" aria-hidden="true"></span>
|
||||
{{ newBtnLabel }}
|
||||
</button>
|
||||
<button v-if="reload" class="btn btn-outline-secondary" aria-label="Reload" @click="reloadTable">
|
||||
<span class="fa-solid fa-rotate-right" aria-hidden="true"></span>
|
||||
</button>
|
||||
<span v-if="$slots.actions && tabulatorHasSelector">Mit {{selectedData.length}} ausgewählten: </span>
|
||||
<slot name="actions" v-bind="tabulatorHasSelector ? selectedData : []"></slot>
|
||||
</div>
|
||||
<div class="d-flex gap-1 align-items-baseline flex-grow-1 justify-content-end">
|
||||
<span v-if="!tableOnly">[ {{ filterName }} ]</span>
|
||||
<a v-if="!tableOnly" href="#" class="btn btn-link px-0 text-dark" data-bs-toggle="collapse" :data-bs-target="'#collapseFilters' + idExtra">
|
||||
<span class="fa-solid fa-xl fa-filter"></span>
|
||||
</a>
|
||||
<a href="#" class="btn btn-link px-0 text-dark" data-bs-toggle="collapse" :data-bs-target="'#collapseColumns' + idExtra">
|
||||
<span class="fa-solid fa-xl fa-table-columns"></span>
|
||||
</a>
|
||||
<table-download class="btn btn-link px-0 text-dark" :tabulator="tabulator" :config="download"></table-download>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!tableOnly" :id="'collapseFilters' + idExtra" class="card-body collapse" :data-bs-parent="'#filterCollapsables' + idExtra">
|
||||
<div class="card">
|
||||
<!-- Filter options -->
|
||||
<div class="card-body" v-if="!sideMenu">
|
||||
<select
|
||||
class="form-select"
|
||||
@change="handlerGetFilterById"
|
||||
>
|
||||
<option value="">Bitte auswählen...</option>
|
||||
<template v-for="availableFilter in availableFilters">
|
||||
<option v-bind:value="availableFilter.option">{{ availableFilter.description }}</option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<div class="card-body filter-options-div">
|
||||
<div>
|
||||
<span>
|
||||
Neuer Filter
|
||||
</span>
|
||||
<span>
|
||||
<button class="btn btn-outline-dark" type="button" @click=handlerAddNewFilter>+</button>
|
||||
</span>
|
||||
</div>
|
||||
<div :id="'filterFields' + idExtra" class="filter-filter-fields">
|
||||
<template v-for="(filterField, index) in filterFields">
|
||||
<div class="row">
|
||||
<filter-columns
|
||||
:id="'collapseColumns' + idExtra"
|
||||
class="card-body collapse"
|
||||
:data-bs-parent="'#filterCollapsables' + idExtra"
|
||||
:fields="fieldIdsForVisibilty"
|
||||
:selected="selectedFields"
|
||||
:names="fieldNames"
|
||||
@hide="tabulator.hideColumn($event)"
|
||||
@show="tabulator.showColumn($event)"
|
||||
></filter-columns>
|
||||
|
||||
<div class="col-5">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Filter {{ index + 1 }}</span>
|
||||
<select
|
||||
class="form-select"
|
||||
name="fieldName"
|
||||
v-bind:value="filterField.name"
|
||||
@change="handlerChangeFilterField(filterField.name, $event.target.value)"
|
||||
>
|
||||
<option value="">Feld zum Filter hinzufügen...</option>
|
||||
<template v-for="columnAlias in filteredColumns">
|
||||
<option v-bind:value="columnAlias.field">{{ columnAlias.title }}</option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Numeric -->
|
||||
<template
|
||||
v-if="filterField.type.toLowerCase().indexOf('int') >= 0">
|
||||
<div class="col-2">
|
||||
<select class="form-select" name="operation" v-model="filterField.operation">
|
||||
<option value="equal">Gleich</option>
|
||||
<option value="nequal">Nicht gleich</option>
|
||||
<option value="gt">Größer als</option>
|
||||
<option value="lt">Weniger als</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<input type="number" class="form-control" v-bind:value="filterField.condition" name="condition">
|
||||
</div>
|
||||
<div class="col">
|
||||
<button
|
||||
class="btn btn-outline-dark"
|
||||
type="button"
|
||||
v-bind:field-to-remove="filterField.name"
|
||||
@click=handlerRemoveFilterField>
|
||||
 X 
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Text -->
|
||||
<template
|
||||
v-if="filterField.type.toLowerCase().indexOf('varchar') >= 0
|
||||
|| filterField.type.toLowerCase().indexOf('text') >= 0
|
||||
|| filterField.type.toLowerCase().indexOf('bpchar') >= 0">
|
||||
<div class="col-2">
|
||||
<select class="form-select" name="operation" v-model="filterField.operation">
|
||||
<option value="equal">Gleich</option>
|
||||
<option value="nequal">Nicht gleich</option>
|
||||
<option value="contains">Enthält</option>
|
||||
<option value="ncontains">Enthält nicht</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<input type="text" class="form-control" v-bind:value="filterField.condition" name="condition">
|
||||
</div>
|
||||
<div class="col">
|
||||
<button
|
||||
class="btn btn-outline-dark"
|
||||
type="button"
|
||||
v-bind:field-to-remove="filterField.name"
|
||||
@click=handlerRemoveFilterField>
|
||||
 X 
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Timestamp and date -->
|
||||
<template
|
||||
v-if="filterField.type.toLowerCase().indexOf('timestamp') >= 0
|
||||
|| filterField.type.toLowerCase().indexOf('date') >= 0">
|
||||
<div class="col-2">
|
||||
<select class="form-select" name="operation" v-model="filterField.operation">
|
||||
<option value="gt">Größer als</option>
|
||||
<option value="lt">Weniger als</option>
|
||||
<option value="set">Eingestellt ist</option>
|
||||
<option value="nset">Eingestellt nicht ist</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="number" class="form-control" v-bind:value="filterField.condition" name="condition">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<select class="form-select" name="option" v-model="filterField.option">
|
||||
<option value="minutes">Minuten</option>
|
||||
<option value="hours">Stunden</option>
|
||||
<option value="days">Tage</option>
|
||||
<option value="months">Monate</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button
|
||||
class="btn btn-outline-dark"
|
||||
type="button"
|
||||
v-bind:field-to-remove="filterField.name"
|
||||
@click=handlerRemoveFilterField
|
||||
> - </button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Filter save options -->
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<div class="input-group">
|
||||
<input ref="customFilterName" type="text" class="form-control" placeholder="Filternamen eingeben..." :id="'customFilterName' + idExtra">
|
||||
<button type="button" class="btn btn-outline-secondary" @click=handlerSaveCustomFilter>Filter speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-outline-dark" @click=handlerApplyFilterFields>Filter anwenden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<filter-config
|
||||
v-if="!tableOnly"
|
||||
:id="'collapseFilters' + idExtra"
|
||||
class="card-body collapse"
|
||||
:data-bs-parent="'#filterCollapsables' + idExtra"
|
||||
:filters="!sideMenu ? (availableFilters || []) : []"
|
||||
:columns="columnsForFilter"
|
||||
:fields="filterFields || []"
|
||||
@switch-filter="switchFilter"
|
||||
@apply-filter-config="applyFilterConfig"
|
||||
@save-custom-filter="handlerSaveCustomFilter"
|
||||
></filter-config>
|
||||
</div>
|
||||
|
||||
<!-- Tabulator -->
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (C) 2022 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
fields: Array,
|
||||
selected: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
names: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
emits: {
|
||||
hide: ['fieldName'],
|
||||
show: ['fieldName']
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
selectedFields: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selected(n) {
|
||||
this.selectedFields = n;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle(field) {
|
||||
if (this.selectedFields.indexOf(field) != -1)
|
||||
{
|
||||
this.selectedFields.splice(this.selectedFields.indexOf(field), 1);
|
||||
this.$emit('hide', field);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.selectedFields.push(field);
|
||||
this.$emit('show', field);
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="filter-columns">
|
||||
<div class="card">
|
||||
<div class="d-flex flex-wrap gap-2 p-3 justify-content-center">
|
||||
<div
|
||||
v-for="fieldToDisplay in fields"
|
||||
class="btn"
|
||||
:class="selectedFields.indexOf(fieldToDisplay) != -1 ? 'btn-dark' : 'btn-outline-dark' "
|
||||
@click="toggle(fieldToDisplay)"
|
||||
>
|
||||
{{ names[fieldToDisplay] || fieldToDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Copyright (C) 2022 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const FILTER_COMPONENT_NEW_FILTER = 'Filter Component New Filter';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
emits: {
|
||||
switchFilter: ['filterId'],
|
||||
applyFilterConfig: ['filterFields'],
|
||||
saveCustomFilter: ['customFilterName']
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
currentFields: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
types() {
|
||||
return this.columns.reduce((a,c) => {
|
||||
let type = c.type.toLowerCase();
|
||||
if (type.indexOf('int') >= 0)
|
||||
a[c.name] = 'Numeric';
|
||||
else if (
|
||||
type.indexOf('varchar') >= 0 ||
|
||||
type.indexOf('text') >= 0 ||
|
||||
type.indexOf('bpchar') >= 0
|
||||
)
|
||||
a[c.name] = 'Text';
|
||||
else if (
|
||||
type.indexOf('timestamp') >= 0 ||
|
||||
type.indexOf('date') >= 0
|
||||
)
|
||||
a[c.name] = 'Date';
|
||||
else
|
||||
a[c.name] = '';
|
||||
return a;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fields(n) {
|
||||
this.currentFields = n;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
switchFilter(evt) {
|
||||
this.$emit('switchFilter', evt.currentTarget.value);
|
||||
},
|
||||
applyFilterConfig() {
|
||||
const filteredFields = this.currentFields.filter(el => el.name != FILTER_COMPONENT_NEW_FILTER);
|
||||
if (filteredFields.filter(el => el.condition == "").length)
|
||||
alert("Please fill all the filter options");
|
||||
else
|
||||
this.$emit('applyFilterConfig', filteredFields);
|
||||
},
|
||||
addField(evt) {
|
||||
this.currentFields.push({
|
||||
name: FILTER_COMPONENT_NEW_FILTER
|
||||
});
|
||||
},
|
||||
removeField(index) {
|
||||
this.currentFields.splice(index, 1);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="filter-config">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div v-if="filters.length" class="mb-4">
|
||||
<select class="form-select" @change="switchFilter">
|
||||
<option value="">Bitte auswählen...</option>
|
||||
<option v-for="filter in filters" :value="filter.id">
|
||||
{{ filter.description }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-outline-dark" type="button" @click=addField>
|
||||
<span class="fa-solid fa-plus" aria-hidden="true"></span> Neuer Filter
|
||||
</button>
|
||||
</div>
|
||||
<div class="filter-config-fields my-3">
|
||||
<div v-for="(filterField, index) in currentFields" class="filter-config-field row">
|
||||
|
||||
<div class="col-5">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Filter {{ index + 1 }}</span>
|
||||
<select
|
||||
class="form-select"
|
||||
v-model="filterField.name"
|
||||
>
|
||||
<option value="">Feld zum Filter hinzufügen...</option>
|
||||
<option v-for="col in columns" :value="col.name">
|
||||
{{ col.title }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Numeric -->
|
||||
<template v-if="types[filterField.name] == 'Numeric'">
|
||||
<div class="col-2">
|
||||
<select class="form-select" v-model="filterField.operation">
|
||||
<option value="equal">Gleich</option>
|
||||
<option value="nequal">Nicht gleich</option>
|
||||
<option value="gt">Größer als</option>
|
||||
<option value="lt">Weniger als</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<input type="number" class="form-control" v-model="filterField.condition">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Text -->
|
||||
<template v-if="types[filterField.name] == 'Text'">
|
||||
<div class="col-2">
|
||||
<select class="form-select" v-model="filterField.operation">
|
||||
<option value="equal">Gleich</option>
|
||||
<option value="nequal">Nicht gleich</option>
|
||||
<option value="contains">Enthält</option>
|
||||
<option value="ncontains">Enthält nicht</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<input type="text" class="form-control" v-model="filterField.condition">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Timestamp and date -->
|
||||
<template v-if="types[filterField.name] == 'Date'">
|
||||
<div class="col-2">
|
||||
<select class="form-select" v-model="filterField.operation">
|
||||
<option value="gt">Größer als</option>
|
||||
<option value="lt">Weniger als</option>
|
||||
<option value="set">Eingestellt ist</option>
|
||||
<option value="nset">Eingestellt nicht ist</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="number" class="form-control" v-model="filterField.condition">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<select class="form-select" v-model="filterField.option">
|
||||
<option value="minutes">Minuten</option>
|
||||
<option value="hours">Stunden</option>
|
||||
<option value="days">Tage</option>
|
||||
<option value="months">Monate</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="col text-end">
|
||||
<button
|
||||
class="btn btn-outline-dark"
|
||||
type="button"
|
||||
@click="removeField(index)"
|
||||
title="Filter entfernen"
|
||||
aria-title="Filter entfernen"
|
||||
>
|
||||
<span class="fa-solid fa-minus" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter save options -->
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<div class="input-group">
|
||||
<input ref="filterName" type="text" class="form-control" placeholder="Filternamen eingeben...">
|
||||
<button type="button" class="btn btn-outline-secondary" @click="$emit('saveCustomFilter', $refs.filterName.value)">Filter speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-outline-dark" @click="applyFilterConfig">Filter anwenden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright (C) 2022 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const DEFAULT_ICONS = {
|
||||
jsonLines: 'fa-file-lines',
|
||||
xlsx: 'fa-file-excel',
|
||||
pdf: 'fa-file-pdf',
|
||||
html: 'fa-file-code',
|
||||
json: 'fa-file',
|
||||
csv: 'fa-file-csv'
|
||||
};
|
||||
const DEFAULT_LABELS = {
|
||||
jsonLines: 'Download as JSONLINES',
|
||||
xlsx: 'Download as XLSX',
|
||||
pdf: 'Download as PDF',
|
||||
html: 'Download as HTML',
|
||||
json: 'Download as JSON',
|
||||
csv: 'Download as CSV '
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
tabulator: Object,
|
||||
config: {
|
||||
type: [Boolean, String, Function, Array, Object],
|
||||
default: false
|
||||
},
|
||||
iconClass: [String, Array, Object]
|
||||
},
|
||||
computed: {
|
||||
currentConfig() {
|
||||
if (!this.config)
|
||||
return false;
|
||||
|
||||
let config = this.config;
|
||||
|
||||
if (config instanceof Function)
|
||||
return [config];
|
||||
|
||||
if (config === null)
|
||||
return [];
|
||||
|
||||
if (this.config === true)
|
||||
config = ['csv'];
|
||||
|
||||
if (Object.prototype.toString.call(config) === "[object String]")
|
||||
config = config.split(',');
|
||||
|
||||
if (typeof config === 'object' && !Array.isArray(config)) {
|
||||
let newConfig = [];
|
||||
for (var k in config) {
|
||||
var v = config[k], type;
|
||||
|
||||
if (!v)
|
||||
continue;
|
||||
|
||||
if (Object.prototype.toString.call(v) === "[object String]") {
|
||||
type = this.stringToFileFormatter(v);
|
||||
if (type !== null) {
|
||||
newConfig.push({
|
||||
icon: 'fa-solid ' + DEFAULT_ICONS[type],
|
||||
label: v === k ? DEFAULT_LABELS[type] : k,
|
||||
formatter: type
|
||||
});
|
||||
} else {
|
||||
type = this.stringToFileFormatter(k);
|
||||
if(type !== null) {
|
||||
newConfig.push({
|
||||
icon: 'fa-solid ' + DEFAULT_ICONS[type],
|
||||
label: v,
|
||||
formatter: type
|
||||
});
|
||||
} else {
|
||||
alert('neither ' + k + ' nor ' + v + ' are supported download file types');
|
||||
}
|
||||
}
|
||||
} else if (typeof v === 'object' && !Array.isArray(v)) {
|
||||
type = this.stringToFileFormatter(k);
|
||||
if (type !== null) {
|
||||
if (v.formatter === undefined)
|
||||
v.formatter = type;
|
||||
if (v.label === undefined)
|
||||
v.label = DEFAULT_LABELS[type];
|
||||
if (v.icon === undefined)
|
||||
v.icon = DEFAULT_ICONS[type];
|
||||
newConfig.push(v);
|
||||
} else {
|
||||
if (v.label === undefined)
|
||||
v.label = k;
|
||||
newConfig.push(v);
|
||||
}
|
||||
} else {
|
||||
type = this.stringToFileFormatter(k);
|
||||
if (type !== null) {
|
||||
newConfig.push({
|
||||
icon: 'fa-solid ' + DEFAULT_ICONS[type],
|
||||
label: DEFAULT_LABELS[type],
|
||||
formatter: type
|
||||
});
|
||||
} else {
|
||||
alert(k + ' is not a supported download file type');
|
||||
}
|
||||
}
|
||||
}
|
||||
config = newConfig;
|
||||
}
|
||||
|
||||
if (Array.isArray(config))
|
||||
{
|
||||
config = config.map(el => {
|
||||
if (Object.prototype.toString.call(el) === "[object String]") {
|
||||
let formatter = this.stringToFileFormatter(el);
|
||||
if (formatter === null)
|
||||
return null;
|
||||
return {
|
||||
icon: 'fa-solid ' + DEFAULT_ICONS[formatter],
|
||||
label: DEFAULT_LABELS[formatter],
|
||||
formatter
|
||||
};
|
||||
}
|
||||
|
||||
if (el instanceof Function)
|
||||
return {
|
||||
formatter: el
|
||||
}
|
||||
|
||||
if (typeof el === 'object' && !Array.isArray(el) && el !== null) {
|
||||
if (el.formatter instanceof Function)
|
||||
return el;
|
||||
if (this.validateFileFormatter(el.formatter))
|
||||
return el;
|
||||
}
|
||||
|
||||
return null;
|
||||
}).filter(el => el !== null);
|
||||
|
||||
if (config.length < 2)
|
||||
return config;
|
||||
|
||||
if (config.filter(el => el.label || el.icon).length == config.length)
|
||||
return config;
|
||||
|
||||
alert('Config not valid');
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
stringToFileFormatter(input) {
|
||||
let lcInput = input.toLowerCase();
|
||||
|
||||
if (lcInput == 'jsonlines')
|
||||
return 'jsonLines';
|
||||
|
||||
if (['xlsx', 'pdf', 'html', 'json', 'csv'].includes(lcInput))
|
||||
return lcInput;
|
||||
|
||||
return null;
|
||||
},
|
||||
validateFileFormatter(input) {
|
||||
let formatter = this.stringToFileFormatter(input);
|
||||
if (!formatter) {
|
||||
alert(input + ' is not a supported file formatter');
|
||||
return false;
|
||||
}
|
||||
if (formatter == 'xlsx') {
|
||||
if (!window.XLSX) {
|
||||
alert('XLSX Library not loaded');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (formatter == 'pdf') {
|
||||
if (!window.jspdf) {
|
||||
alert('jsPDF Library not loaded');
|
||||
return false;
|
||||
}
|
||||
var doc = new jspdf.jsPDF({});
|
||||
if (!doc.autoTable) {
|
||||
alert('jsPDF-AutoTable Plugin not loaded');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
download(config) {
|
||||
this.tabulator.download(config.formatter, config.file, config.options)
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<template v-if="currentConfig">
|
||||
<template v-if="currentConfig.length == 1">
|
||||
<a
|
||||
href="#"
|
||||
class="table-download"
|
||||
v-bind="$attrs"
|
||||
title="Download"
|
||||
aria-title="Download"
|
||||
@click.prevent="download(currentConfig[0])"
|
||||
>
|
||||
<span :class="iconClass || 'fa-solid fa-xl fa-download'" aria-hidden="true"></span>
|
||||
</a>
|
||||
</template>
|
||||
<div v-else class="dropdown d-inline">
|
||||
<a
|
||||
href="#"
|
||||
class="table-download"
|
||||
v-bind="$attrs"
|
||||
title="Download"
|
||||
aria-title="Download"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span :class="iconClass || 'fa-solid fa-xl fa-download'" aria-hidden="true"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li v-for="(conf, i) in currentConfig" :key="i">
|
||||
<a class="dropdown-item" href="#" @click.prevent="download(conf)">
|
||||
<span v-if="conf.icon" :class="conf.icon" aria-hidden="true"></span>
|
||||
{{conf.label}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
`
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (C) 2022 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: '',
|
||||
subtitle: '',
|
||||
mainCols: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
asideCols: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
mainGridCols() {
|
||||
return this.mainCols.length > 0 ? `col-md-${this.mainCols[0]}` : "col-md-12";
|
||||
},
|
||||
asideGridCols() {
|
||||
return this.asideCols.length > 0 ? `col-md-${this.asideCols[0]}` : "";
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="overflow-hidden">
|
||||
<header v-if="title">
|
||||
<h1 class="h2 mb-5">{{ title }}<span class="fhc-subtitle">{{ subtitle }}</span></h1>
|
||||
</header>
|
||||
<div class="row gx-5">
|
||||
<main :class="mainGridCols">
|
||||
<slot name="main">{{ mainGridCols }}</slot>
|
||||
</main>
|
||||
<aside v-if="asideCols.length > 0" :class="asideGridCols">
|
||||
<slot name="aside">{{ asideGridCols }}</slot>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
@@ -18,10 +18,10 @@
|
||||
import {CoreRESTClient} from '../../RESTClient.js';
|
||||
|
||||
//
|
||||
const CORE_NAVIGATION_CMPT_TIMEOUT = 2000;
|
||||
const CORE_NAVIGATION_CMPT_TIMEOUT = 5000;
|
||||
|
||||
/**
|
||||
*
|
||||
* TODO(chris): deprecated
|
||||
*/
|
||||
export const CoreNavigationAPIs = {
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (C) 2022 fhcomplete.org
|
||||
* Copyright (C) 2024 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,8 +15,6 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {CoreNavigationAPIs} from './API.js';
|
||||
import {CoreRESTClient} from '../../RESTClient.js';
|
||||
import {CoreFetchCmpt} from '../../components/Fetch.js';
|
||||
|
||||
/**
|
||||
@@ -30,12 +28,12 @@ export const CoreNavigationCmpt = {
|
||||
addHeaderMenuEntries: Object, // property used to add new header menu entries from another app/component
|
||||
addSideMenuEntries: Object, // property used to add new side menu entries from another app/component
|
||||
hideTopMenu: Boolean,
|
||||
leftNavCssClasses: {
|
||||
type: String,
|
||||
default: 'navbar navbar-left-side'
|
||||
}
|
||||
leftNavCssClasses: {
|
||||
type: String,
|
||||
default: 'navbar navbar-left-side'
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
data() {
|
||||
return {
|
||||
headerMenu: {}, // header menu entries
|
||||
sideMenu: {} // side menu entries
|
||||
@@ -45,61 +43,63 @@ export const CoreNavigationCmpt = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
headerMenuEntries: function() {
|
||||
headerMenuEntries() {
|
||||
//
|
||||
let hm = this.headerMenu ? {...this.headerMenu} : {};
|
||||
if (this.headerMenu != null && this.addHeaderMenuEntries != null && Object.keys(this.addHeaderMenuEntries).length > 0)
|
||||
{
|
||||
this.headerMenu[this.addHeaderMenuEntries.description] = this.addHeaderMenuEntries;
|
||||
hm[this.addHeaderMenuEntries.description] = this.addHeaderMenuEntries;
|
||||
}
|
||||
return this.headerMenu;
|
||||
return hm;
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
sideMenuEntries: function() {
|
||||
sideMenuEntries() {
|
||||
//
|
||||
let sm = this.sideMenu ? {...this.sideMenu} : {};
|
||||
if (this.sideMenu != null && this.addSideMenuEntries != null && Object.keys(this.addSideMenuEntries).length > 0)
|
||||
{
|
||||
this.sideMenu[this.addSideMenuEntries.description] = this.addSideMenuEntries;
|
||||
sm[this.addSideMenuEntries.description] = this.addSideMenuEntries;
|
||||
}
|
||||
return this.sideMenu;
|
||||
return sm;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
getNavigationPage: function() {
|
||||
getNavigationPage() {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.called_path + "/" + FHC_JS_DATA_STORAGE_OBJECT.called_method;
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fetchCmptApiFunctionHeader: function() {
|
||||
return CoreNavigationAPIs.getHeader(this.getNavigationPage());
|
||||
fetchCmptApiFunctionHeader() {
|
||||
return this.$fhcApi.factory.navigation.getHeader(this.getNavigationPage());
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fetchCmptApiFunctionSideMenu: function() {
|
||||
return CoreNavigationAPIs.getMenu(this.getNavigationPage());
|
||||
fetchCmptApiFunctionSideMenu() {
|
||||
return this.$fhcApi.factory.navigation.getMenu(this.getNavigationPage());
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fetchCmptDataFetchedHeader: function(data) {
|
||||
if (CoreRESTClient.hasData(data)) this.headerMenu = CoreRESTClient.getData(data);
|
||||
fetchCmptDataFetchedHeader(data) {
|
||||
this.headerMenu = data || {};
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fetchCmptDataFetchedMenu: function(data) {
|
||||
if (CoreRESTClient.hasData(data)) this.sideMenu = CoreRESTClient.getData(data);
|
||||
fetchCmptDataFetchedMenu(data) {
|
||||
this.sideMenu = data || {};
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
getDataBsToggle: function(header) {
|
||||
getDataBsToggle(header) {
|
||||
return !header.children ? null : 'dropdown';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
<div class="searchbar_icon">
|
||||
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
|
||||
<img v-if="(typeof res.photo_url !== 'undefined') && (res.photo_url !== null)" :src="res.photo_url"
|
||||
class="rounded-circle" height="100" />
|
||||
class="rounded" style="max-height: 120px; max-width: 90px;" />
|
||||
<i v-else class="fas fa-user-circle fa-5x"></i>
|
||||
</action>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
<div class="searchbar_icon">
|
||||
<action :res="this.res" :action="this.actions.defaultaction" @actionexecuted="$emit('actionexecuted')">
|
||||
<img v-if="(typeof res.foto !== 'undefined') && (res.foto !== null)" :src="res.foto"
|
||||
class="rounded-circle" height="100" />
|
||||
class="rounded" style="max-height: 120px; max-width: 90px;" />
|
||||
<i v-else class="fas fa-user-circle fa-5x"></i>
|
||||
</action>
|
||||
</div>
|
||||
|
||||
@@ -27,13 +27,13 @@ export default {
|
||||
organisationunit: organisationunit
|
||||
},
|
||||
template: `
|
||||
<form ref="searchform" class="d-flex me-3" action="javascript:void(0);"
|
||||
<form ref="searchform" class="d-flex me-3 position-relative" action="javascript:void(0);"
|
||||
@focusin="this.searchfocusin" @focusout="this.searchfocusout">
|
||||
<div class="input-group me-2 bg-white">
|
||||
<input ref="searchbox" @keyup="this.search" @focus="this.showsearchresult"
|
||||
v-model="this.searchsettings.searchstr" class="form-control"
|
||||
type="search" placeholder="Search" aria-label="Search">
|
||||
<button ref="settingsbutton" @click="this.togglesettings" class="btn btn-outline-secondary" type="button" id="search-filter"><i class="fas fa-cog"></i></button>
|
||||
v-model="this.searchsettings.searchstr" class="form-control"
|
||||
type="search" placeholder="Suche..." aria-label="Search">
|
||||
<button ref="settingsbutton" @click="this.togglesettings" class="btn btn-light border-start" type="button" id="search-filter"><i class="fas fa-cog"></i></button>
|
||||
</div>
|
||||
|
||||
<div v-show="this.showresult" ref="result"
|
||||
@@ -46,6 +46,7 @@ export default {
|
||||
<template v-else="" v-for="res in this.searchresult">
|
||||
<person v-if="res.type === 'person'" :res="res" :actions="this.searchoptions.actions.person" @actionexecuted="this.hideresult"></person>
|
||||
<employee v-else-if="res.type === 'mitarbeiter'" :res="res" :actions="this.searchoptions.actions.employee" @actionexecuted="this.hideresult"></employee>
|
||||
<employee v-else-if="res.type === 'mitarbeiter_ohne_zuordnung'" :res="res" :actions="this.searchoptions.actions.employee" @actionexecuted="this.hideresult"></employee>
|
||||
<organisationunit v-else-if="res.type === 'organisationunit'" :res="res" :actions="this.searchoptions.actions.organisationunit" @actionexecuted="this.hideresult"></organisationunit>
|
||||
<raum v-else-if="res.type === 'raum'" :res="res" :actions="this.searchoptions.actions.raum" @actionexecuted="this.hideresult"></raum>
|
||||
<div v-else="">Unbekannter Ergebnistyp: '{{ res.type }}'.</div>
|
||||
@@ -78,16 +79,13 @@ export default {
|
||||
calcSearchResultExtent: function() {
|
||||
var rect = this.$refs.searchbox.getBoundingClientRect();
|
||||
//console.log(window.innerWidth + ' ' + window.innerHeight + ' ' + JSON.stringify(rect));
|
||||
this.$refs.result.style.top = Math.floor(rect.bottom + 3) + 'px';
|
||||
this.$refs.result.style.right = Math.floor(window.innerWidth - rect.right) + 'px';
|
||||
this.$refs.result.style.width = Math.floor(window.innerWidth * 0.75) + 'px';
|
||||
this.$refs.result.style.height = Math.floor(window.innerHeight * 0.75) + 'px';
|
||||
this.$refs.result.style.height = Math.floor(window.innerHeight * 0.80) + 'px';
|
||||
},
|
||||
search: function() {
|
||||
if( this.searchtimer !== null ) {
|
||||
clearTimeout(this.searchtimer);
|
||||
}
|
||||
if( this.searchsettings.searchstr.length >= 3 ) {
|
||||
if( this.searchsettings.searchstr.length >= 2 ) {
|
||||
this.calcSearchResultExtent();
|
||||
this.searchtimer = setTimeout(
|
||||
this.callsearchapi,
|
||||
@@ -105,7 +103,11 @@ export default {
|
||||
this.showsearchresult();
|
||||
this.searchfunction(this.searchsettings)
|
||||
.then(function(response) {
|
||||
that.searchresult = response.data.data;
|
||||
if( response.data?.error === 1 ) {
|
||||
that.error = 'Bei der Suche ist ein Fehler aufgetreten.';
|
||||
} else {
|
||||
that.searchresult = response.data.data;
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
that.error = 'Bei der Suche ist ein Fehler aufgetreten.'
|
||||
|
||||
@@ -23,13 +23,13 @@ export default {
|
||||
:class="this.topOrBottomClass" @mousedown="this.dragStart">
|
||||
<div class="splitactions" :class="this.topOrBottomClass">
|
||||
<span @click="this.collapseTop" class="splitaction">
|
||||
<i class="fas fa-angle-up"></i>
|
||||
<i class="fas fa-angle-up text-muted"></i>
|
||||
</span>
|
||||
<span @dblclick="this.showBoth" class="splitaction resize">
|
||||
<i class="fas fa-grip-horizontal"></i>
|
||||
<i class="fas fa-grip-lines text-muted"></i>
|
||||
</span>
|
||||
<span @click="this.collapseBottom" class="splitaction">
|
||||
<i class="fas fa-angle-down"></i>
|
||||
<i class="fas fa-angle-down text-muted"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
$path = "../../../vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js";
|
||||
|
||||
if(file_exists("../../../vendor/vuepic/vue-datepicker-js/vue-datepicker.iife.js"))
|
||||
if(file_exists($path))
|
||||
{
|
||||
header('Content-Type: application/javascript');
|
||||
echo file_get_contents("../../../vendor/vuepic/vue-datepicker-js/vue-datepicker.iife.js");
|
||||
echo file_get_contents($path);
|
||||
echo "export default VueDatePicker";
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user