cis4 abgabetool student uploads/signature check/emails working; TODO: - testing with diff users, styling & mobile layout;

This commit is contained in:
Johann Hoffmann
2025-03-19 15:57:08 +01:00
parent 0dd1e4a628
commit 2759bb0309
11 changed files with 995 additions and 257 deletions
@@ -6,7 +6,11 @@ export const AbgabeDetail = {
name: "AbgabeDetail",
components: {
Upload,
BsModal
BsModal,
InputNumber: primevue.inputnumber,
Checkbox: primevue.checkbox,
Dropdown: primevue.dropdown,
Textarea: primevue.textarea
},
props: {
projektarbeit: {
@@ -16,23 +20,101 @@ export const AbgabeDetail = {
},
data() {
return {
file: []
eidAkzeptiert: false,
enduploadTermin: null,
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
form: Vue.reactive({
sprache: '',
abstract: '',
abstract_en: '',
schlagwoerter: '',
schlagwoerter_en: '',
kontrollschlagwoerter: '',
seitenanzahl: 1,
})
}
},
methods: {
triggerUpload() {
// todo: trigger the loadup
validate: function(termin) {
if(!termin.file.length) {
this.$fhcAlert.alertWarning(this.$p.t('global/warningChooseFile'));
return false
}
return true;
},
triggerEndupload() {
if (!this.validate(this.enduploadTermin))
{
return false;
}
// post endabgabe
const formData = new FormData();
formData.append('paabgabetyp_kurzbz', this.enduploadTermin.paabgabetyp_kurzbz)
formData.append('projektarbeit_id', this.enduploadTermin.projektarbeit_id);
formData.append('paabgabe_id', this.enduploadTermin.paabgabe_id)
formData.append('student_uid', this.projektarbeit.student_uid)
formData.append('bperson_id', this.projektarbeit.bperson_id)
// TODO: validate/check for null etc.
formData.append('sprache', this.form['sprache'].sprache)
formData.append('abstract', this.form['abstract'])
formData.append('abstract_en', this.form['abstract_en'])
formData.append('schlagwoerter', this.form['schlagwoerter'])
formData.append('schlagwoerter_en', this.form['schlagwoerter_en'])
formData.append('seitenanzahl', this.form['seitenanzahl'])
for (let i = 0; i < this.enduploadTermin.file.length; i++) {
formData.append('file', this.enduploadTermin.file[i]);
}
this.$fhcApi.factory.lehre.postStudentProjektarbeitEndupload(formData)
.then(res => {
this.handleUploadRes(res)
})
this.$refs.modalContainerEnduploadZusatzdaten.hide()
},
downloadAbgabe(termin) {
this.$fhcApi.factory.lehre.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid)
},
upload(termin) {
console.log(termin)
// TODO load it up
if (!this.validate(termin))
{
return false;
}
if(termin.bezeichnung === 'Endupload') {
// open endupload form modal and await that it will be sent & checked
// open endupload form modal for further inputs
this.enduploadTermin = termin
this.$refs.modalContainerEnduploadZusatzdaten.show()
} else {
const formData = new FormData();
formData.append('paabgabetyp_kurzbz', termin.paabgabetyp_kurzbz)
formData.append('projektarbeit_id', this.projektarbeit.projektarbeit_id)
formData.append('paabgabe_id', termin.paabgabe_id)
formData.append('student_uid', this.projektarbeit.student_uid)
formData.append('bperson_id', this.projektarbeit.bperson_id)
for (let i = 0; i < termin.file.length; i++) {
formData.append('file', termin.file[i]);
}
this.$fhcApi.factory.lehre.postStudentProjektarbeitZwischenabgabe(formData)
.then(res => {
this.handleUploadRes(res)
})
}
},
handleUploadRes(res) {
if(res.meta.status == "success") {
this.$fhcAlert.alertSuccess('File erfolgreich hochgeladen')
} else {
this.$fhcAlert.alertError('File upload error')
}
if(res.meta.signaturInfo) {
this.$fhcAlert.alertInfo(res.meta.signaturInfo)
}
},
dateDiffInDays(datum, today){
@@ -43,6 +125,7 @@ export const AbgabeDetail = {
const datum = new Date(termin.datum)
const abgabedatum = new Date(termin.abgabedatum)
// todo: rework styling but keep the color pattern logic
// https://wiki.fhcomplete.info/doku.php?id=cis:abgabetool_fuer_studierende
let color = 'white'
let fontColor = 'black'
@@ -61,28 +144,33 @@ export const AbgabeDetail = {
}
return 'font-color: ' + fontColor + '; background-color: ' + color
},
openBeurteilungLink(link) {
window.open(link, '_blank')
},
getOptionLabel(option) {
return option.sprache
}
},
watch: {
'file'(newVal) {
if(newVal == [] || newVal === null || newVal === undefined) return
// check filetype on input change
const file = newVal[0]
if(!file) return
if(file.type && file.type.includes('pdf')) {
// all fine
} else {
// clear and alert for filetypes
this.$fhcAlert.alertInfo(this.$p.t('abgabetool/c4allowedFileTypes'))
this.entschuldigung.files = []
}
projektarbeit(newVal) {
// default select german if projektarbeit sprache was null
this.form.sprache = newVal.sprache ? this.allActiveLanguages.find(lang => lang.sprache == newVal.sprache) : this.allActiveLanguages.find(lang => lang.sprache == 'German')
this.form.abstract = newVal.abstract
this.form.abstract_en = newVal.abstract_en
this.form.schlagwoerter = newVal.schlagwoerter
this.form.schlagwoerter_en = newVal.schlagwoerter_en
this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter
this.form.seitenanzahl = newVal.seitenanzahl
}
},
computed: {
getEid() {
return this.$p.t('abgabetool/c4eidesstattlicheErklaerung')
},
getEnduploadErlaubt() {
return !this.eidAkzeptiert
}
},
created() {
@@ -100,33 +188,46 @@ export const AbgabeDetail = {
</div>
<div id="uploadWrapper">
<div class="row" style="margin-bottom: 12px;">
<div class="col">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div style="width: 100px">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col">
{{$p.t('abgabetool/c4fileupload')}}
</div>
<div class="col">
</div>
</div>
<div class="row" v-for="termin in projektarbeit.abgabetermine">
<div class="col d-flex justify-content-center align-items-center">
<div style="width: 100px" class="d-flex justify-content-center align-items-center">
<p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>
</div>
<div class="col" :style="getDateStyle(termin)">{{ termin.datum?.split("-").reverse().join(".") }}</div>
<div class="col">{{ termin.bezeichnung }}</div>
<div class="col">{{ termin.kurzbz }}</div>
<div class="col">{{ termin.abgabedatum?.split("-").reverse().join(".") }}</div>
<div class="col">
<!-- <Upload accept=".pdf" v-model="file"></Upload>-->
<div class="col-1 d-flex justify-content-center align-items-center">
<div :style="getDateStyle(termin)">
{{ termin.datum?.split("-").reverse().join(".") }}
</div>
</div>
<div class="col">
<button class="btn btn-primary border-0" @click="upload(termin)">
Upload
<i style="margin-left: 8px" class="fa-solid fa-upload"></i>
</button>
<div class="col-1 d-flex justify-content-center align-items-center">{{ termin.bezeichnung }}</div>
<div class="col-2 d-flex justify-content-center align-items-center">{{ termin.kurzbz }}</div>
<div class="col-1 d-flex justify-content-center align-items-center">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="cursor: pointer;">
<i class="fa-solid fa-file-pdf"></i>
</a>
</div>
<div class="col-6">
<div class="row">
<div class="col-6">
<Upload v-if="termin && termin.allowedToUpload" accept=".pdf" v-model="termin.file"></Upload>
</div>
<div class="col-6">
<button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload">
Upload
<i style="margin-left: 8px" class="fa-solid fa-upload"></i>
</button>
</div>
</div>
</div>
</div>
</div>
@@ -136,58 +237,98 @@ export const AbgabeDetail = {
<template v-slot:title>
<div>
{{$p.t('abgabetool/c4enduploadZusatzdaten')}}
</div>
<div class="row mb-3 align-items-center">
student uid
<p class="ml-4 mr-4">Student UID: {{ projektarbeit?.student_uid}}</p>
</div>
<div class="row mb-3 align-items-center">
{{ projektarbeit?.titel }}
<p class="ml-4 mr-4">Titel: {{ projektarbeit?.titel }}</p>
</div>
</template>
<template v-slot:default>
<div class="row mb-3 align-items-center">
Sprache der Arbeit
<div class="row">{{$p.t('abgabetool/c4Sprache')}}</div>
<div class="row">
<Dropdown
:style="{'width': '100%'}"
v-model="form.sprache"
:options="allActiveLanguages"
:optionLabel="getOptionLabel">
</Dropdown>
</div>
</div>
<!-- lektor fills these out-->
<!-- <div class="row mb-3 align-items-center">-->
<!-- <div class="row">Kontrollierte Schlagwörter</div>-->
<!-- <div class="row">-->
<!-- <Textarea v-model="form.kontrollschlagwoerter"></Textarea>-->
<!-- </div>-->
<!-- -->
<!-- -->
<!-- </div>-->
<div class="row mb-3 align-items-center">
Kontrollierte Schlagwörter
<div class="row">{{$p.t('abgabetool/c4schlagwoerterGer')}}</div>
<div class="row">
<Textarea v-model="form.schlagwoerter"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-center">
Dt. Schlagwörter
<div class="row">{{$p.t('abgabetool/c4schlagwoerterEng')}}</div>
<div class="row">
<Textarea v-model="form.schlagwoerter_en"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-center">
Engl. Schlagwörter
<div class="row">{{$p.t('abgabetool/c4abstractGer')}}</div>
<div class="row">
<Textarea v-model="form.abstract" rows="10"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-center">
Abstract max 5k characters
<div class="row">{{$p.t('abgabetool/c4abstractEng')}}</div>
<div class="row">
<Textarea v-model="form.abstract_en" rows="10"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-center">
Abstract eng max 5k chars
<div class="row">{{$p.t('abgabetool/c4seitenanzahl')}}</div>
<div class="row">
<InputNumber
v-model="form.seitenanzahl"
inputId="seitenanzahlInput" :min="1" :max="100000">
</InputNumber>
</div>
</div>
<div class="row mb-3 align-items-center">
Seitenanzahl
<div v-if="projektarbeit">
<div v-html="getEid"></div>
<div class="row">
<div class="col-9"></div>
<div class="col-2"><p>{{ $p.t('abgabetool/c4gelesenUndAkzeptiert') }}</p></div>
<div class="col-1">
<Checkbox
v-model="eidAkzeptiert"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
</div>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-primary" @click="triggerUpload">{{$p.t('ui/hochladen')}}</button>
<button class="btn btn-primary" :disabled="getEnduploadErlaubt" @click="triggerEndupload">{{$p.t('ui/hochladen')}}</button>
</template>
</bs-modal>
@@ -21,17 +21,20 @@ export const Abgabetool = {
data() {
return {
domain: '',
student_uid: null,
detail: null,
projektarbeiten: null,
selectedProjektarbeit: null,
tableBuiltResolve: null,
tableBuiltPromise: null,
abgabeTableOptions: {
height: 200, // TODO: determine smallest necessary height
height: 300,
index: 'projektarbeit_id',
layout: 'fitColumns',
placeholder: this.$p.t('global/noDataAvailable'),
columns: [
{title: Vue.computed(() => this.$p.t('abgabetool/c4details')), formatter: this.detailFormatter, field: 'details', widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4beurteilung')), formatter: this.beurteilungFormatter, field: 'beurteilung', widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4sem')), field: 'sem', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4kontakt')), field: 'mail', formatter: this.mailFormatter, widthGrow: 1},
@@ -50,20 +53,23 @@ export const Abgabetool = {
{
event: "cellClick",
handler: async (e, cell) => {
if(cell.getColumn().getField() === "details") {
const val = cell.getValue()
if(val.mode === 'detailTermine') {
this.setDetailComponent(cell.getValue())
} else if (val.mode === 'beurteilungDownload') {
//demo.dev.technikum-wien.at/cis/private/lehre/projektbeurteilungDocumentExport.php?betreuerart_kurzbz=Begutachter&projektarbeit_id=39239&person_id=22117
const pdfExportLink = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'cis/private/pdfExport.php?xml=projektarbeitsbeurteilung.xml.php&xsl=Projektbeurteilung&betreuerart_kurzbz='+val.betreuerart_kurzbz+'&projektarbeit_id='+val.projektarbeit_id+'&person_id=' + val.betreuer_person_id
const pdfExportLink2 = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'cis/private/lehre/projektbeurteilungDocumentExport.php?betreuerart_kurzbz='+val.betreuerart_kurzbz+'&projektarbeit_id='+val.projektarbeit_id+'&person_id=' + val.betreuer_person_id
// const pdfExportLink2 = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'cis/private/lehre/projektbeurteilungDocumentExport.php?betreuerart_kurzbz='+val.betreuerart_kurzbz+'&projektarbeit_id='+val.projektarbeit_id+'&person_id=' + val.betreuer_person_id
window.open(pdfExportLink, '_blank')
}
}
console.log(cell.getData())
} else if (cell.getColumn().getField() === "beurteilung") {
const val = cell.getValue()
if(val != '-') window.open(val, '_blank')
}
e.stopPropagation()
}
@@ -71,13 +77,29 @@ export const Abgabetool = {
]};
},
methods: {
isPastDate(date) {
return new Date(date) < new Date(Date.now())
},
setDetailComponent(details){
this.loadAbgaben(details).then((res)=> {
console.log(res)
const pa = this.data[0]?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data.retval
pa.abgabetermine.forEach(termin => {
termin.file = []
termin.allowedToUpload = true
// TODO: fixtermin logic?
if(termin.bezeichnung == 'Endupload' && this.isPastDate(termin.datum)) {
// termin.allowedToUpload = false
} else {
// termin.allowedToUpload = true
}
})
pa.betreuer = this.buildBetreuer(pa)
pa.student_uid = this.student_uid
this.selectedProjektarbeit = pa
})
@@ -104,6 +126,13 @@ export const Abgabetool = {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<a href='+val+'><i class="fa fa-envelope" style="color:#00649C"></i></a></div>'
},
beurteilungFormatter(cell) {
const val = cell.getValue()
if(val) {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<a><i class="fa fa-file-pdf" style="color:#00649C"></i></a></div>'
} else return '-'
},
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
@@ -111,28 +140,30 @@ export const Abgabetool = {
return 'mailto:' + abgabe.mitarbeiter_uid +'@'+ this.domain
},
buildBetreuer(abgabe) {
return abgabe.betreuerart_beschreibung + ': ' + (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ?? '')
return abgabe.betreuerart_beschreibung + ': ' + (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
},
setupData(data){
this.data = data // TODO: better define what is needed from this for detail component
this.domain = data[1] // TODO do this in backend but this is a prototype anyway
this.projektarbeiten = data[0]
this.domain = data[1]
this.student_uid = data[2]
const d = data[0]?.retval?.map(projekt => {
console.log('projekt', projekt)
let mode = 'detailTermine'
if (projekt.babgeschickt || projekt.zweitbetreuer_abgeschickt) {
mode = 'beurteilungDownload' // build dl link for both betreuer documents
// mode = 'beurteilungDownload' // build dl link for both betreuer documents
projekt.beurteilungLink = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'cis/private/pdfExport.php?xml=projektarbeitsbeurteilung.xml.php&xsl=Projektbeurteilung&betreuerart_kurzbz='+projekt.betreuerart_kurzbz+'&projektarbeit_id='+projekt.projektarbeit_id+'&person_id=' + projekt.bperson_id
}
return {
details: {
student_uid: this.viewData?.uid,
student_uid: this.student_uid,
projektarbeit_id: projekt.projektarbeit_id,
betreuer_person_id: projekt.bperson_id,
betreuerart_kurzbz: projekt.betreuerart_kurzbz,
mode
},
beurteilung: projekt.beurteilungLink ?? null,
sem: projekt.studiensemester_kurzbz,
stg: projekt.kurzbzlang,
mail: this.buildMailToLink(projekt),