This commit is contained in:
Johann Hoffmann
2025-09-04 11:04:12 +02:00
parent a560c335b8
commit 5f1c7537fb
4 changed files with 221 additions and 85 deletions
@@ -140,13 +140,16 @@ class Abgabe extends FHCAPI_Controller
$projektarbeiten = getData($result);
// TODO: save access to this, array could be empty
foreach($projektarbeiten as $pa) {
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($pa->projektarbeit_id);
// TODO: save access
$pa->email = getData($result)[0]->private_email;
if(count($projektarbeiten)) {
// TODO: save access to this, array could be empty
foreach($projektarbeiten as $pa) {
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($pa->projektarbeit_id);
// TODO: save access
$pa->email = getData($result)[0]->private_email;
}
}
$this->terminateWithSuccess(array($projektarbeiten, DOMAIN, $uid));
}
+1
View File
@@ -233,6 +233,7 @@ const app = Vue.createApp({
return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language),
renderers: Vue.computed(() => this.renderers),
isMobile: this.isMobile
}
},
methods: {
@@ -13,9 +13,11 @@ export const AbgabeStudentDetail = {
Checkbox: primevue.checkbox,
Dropdown: primevue.dropdown,
Textarea: primevue.textarea,
Accordion: primevue.accordion,
AccordionTab: primevue.accordiontab,
VueDatePicker
},
inject: ['notenOptions'],
inject: ['notenOptions', 'isMobile'],
props: {
projektarbeit: {
type: Object,
@@ -184,6 +186,14 @@ export const AbgabeStudentDetail = {
getTerminNoteBezeichnung(termin) {
const noteOpt = this.notenOptions.find(opt => opt.note == termin.note)
return noteOpt ? noteOpt.bezeichnung : ''
},
getAccTabHeaderForTermin(termin) {
let tabTitle = ''
const datumFormatted = this.formatDate(termin.datum)
tabTitle += termin.bezeichnung + ' ' + datumFormatted
return tabTitle
}
},
watch: {
@@ -230,79 +240,175 @@ export const AbgabeStudentDetail = {
<p> {{projektarbeit?.betreuer}}</p>
<p> {{projektarbeit?.titel}}</p>
</div>
<div id="uploadWrapper">
<div class="row" style="margin-bottom: 12px;">
<div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-1 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div v-show="qualityGateTerminAvailable" class="col-1 fw-bold">{{$p.t('abgabetool/c4note')}}</div>
<div v-show="qualityGateTerminAvailable" class="col-1 fw-bold">{{$p.t('abgabetool/c4upload_allowed')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col-3 fw-bold">
{{$p.t('abgabetool/c4fileupload')}}
</div>
</div>
<div class="row" v-for="termin in projektarbeit.abgabetermine">
<div class="col-1 d-flex justify-content-center align-items-start">
<i v-if="termin.fixtermin" class="fa-solid fa-2x fa-circle-check fhc-bullet-red"></i>
<i v-else="" class="fa-solid fa-2x fa-circle-xmark fhc-bullet-green"></i>
<!--
<p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>
-->
</div>
<div class="col-2 d-flex justify-content-start align-items-start">
<div class="position-relative" :class="getDateStyle(termin)">
<VueDatePicker
v-model="termin.datum"
:clearable="false"
:disabled="true"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
<i class="position-absolute abgabe-zieldatum-overlay fa-solid fa-2x" :class="getDateStyle(termin, 'icon')"></i>
</div>
</div>
<div class="col-1 d-flex justify-content-start align-items-start">{{ termin.bezeichnung }}</div>
<div v-if="qualityGateTerminAvailable || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-start align-items-start">
{{ getTerminNoteBezeichnung(termin) }}
</div>
<div v-if="qualityGateTerminAvailable || termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-center align-items-start">
<Checkbox
v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'"
disabled
v-model="termin.upload_allowed"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
<div class="col-2 d-flex justify-content-start align-items-start">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" cols="45" :disabled="true"></Textarea>
</div>
<div class="col-1 d-flex justify-content-start align-items-center">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
<i class="fa-solid fa-2x fa-file-pdf"></i>
</a>
</div>
<div class="col-3" v-if="!viewMode">
<!-- TODO: arrange this properly in mobile view, left col is a bit tight and all-->
<Accordion ref="accordion" :multiple="true" :activeIndex="[0]">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :header="getAccTabHeaderForTermin(termin)" style="padding: 0px;" :pt="tabPassthroughStyle">
<div class="row">
<div class="col-8">
<Upload v-if="termin && termin.allowedToUpload" accept=".pdf" v-model="termin.file"></Upload>
</div>
<div class="col-4">
<button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload">
{{$p.t('abgabetool/c4upload')}}
<i class="fa-solid fa-upload"></i>
</button>
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-8 col-md-9">
<Checkbox
disabled
v-model="termin.fixtermin"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
<!-- <i v-if="termin.fixtermin" class="fa-solid fa-2x fa-circle-check fhc-bullet-red"></i>-->
<!-- <i v-else="" class="fa-solid fa-2x fa-circle-xmark fhc-bullet-green"></i>-->
</div>
</div>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-8 col-md-9">
<VueDatePicker
v-model="termin.datum"
:clearable="false"
:disabled="true"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
<!-- <i class="position-absolute abgabe-zieldatum-overlay fa-solid fa-2x" :class="getDateStyle(termin, 'icon')"></i>-->
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-8 col-md-9">
{{ termin.bezeichnung }}
</div>
</div>
<div class="row mt-2" v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4note')}}</div>
<div class="col-8 col-md-9">
<div v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-start align-items-start">
{{ getTerminNoteBezeichnung(termin) }}
</div>
</div>
</div>
<div class="row mt-2" v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4upload_allowed')}}</div>
<div class="col-8 col-md-9">
<Checkbox
disabled
v-model="termin.upload_allowed"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-8 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" :rows=" isMobile ? 2 : 4" :cols=" isMobile ? 30 : 90" :disabled="true"></Textarea>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col-8 col-md-9">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
<i class="fa-solid fa-2x fa-file-pdf"></i>
</a>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fileupload')}}</div>
<div class="col-8 col-md-9">
<div class="row">
<div class="col-4">
<Upload :disabled="!termin?.allowedToUpload" accept=".pdf" v-model="termin.file"></Upload>
</div>
<div class="col-4">
<button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload">
{{$p.t('abgabetool/c4upload')}}
<i class="fa-solid fa-upload"></i>
</button>
</div>
</div>
</div>
</div>
</AccordionTab>
</template>
</Accordion>
<!-- <div id="uploadWrapper">-->
<!-- <div class="row" style="margin-bottom: 12px;">-->
<!-- <div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4fixtermin')}}</div>-->
<!-- <div class="col-2 fw-bold">{{$p.t('abgabetool/c4zieldatum')}}</div>-->
<!-- <div class="col-1 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>-->
<!-- <div v-show="qualityGateTerminAvailable" class="col-1 fw-bold">{{$p.t('abgabetool/c4note')}}</div>-->
<!-- <div v-show="qualityGateTerminAvailable" class="col-1 fw-bold">{{$p.t('abgabetool/c4upload_allowed')}}</div>-->
<!-- <div class="col-2 fw-bold">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>-->
<!-- <div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4abgabedatum')}}</div>-->
<!-- <div class="col-3 fw-bold">-->
<!-- {{$p.t('abgabetool/c4fileupload')}}-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="row" v-for="termin in projektarbeit.abgabetermine">-->
<!-- <div class="col-1 d-flex justify-content-center align-items-start">-->
<!-- <i v-if="termin.fixtermin" class="fa-solid fa-2x fa-circle-check fhc-bullet-red"></i>-->
<!-- <i v-else="" class="fa-solid fa-2x fa-circle-xmark fhc-bullet-green"></i>-->
<!--&lt;!&ndash;-->
<!-- <p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>-->
<!--&ndash;&gt;-->
<!-- </div>-->
<!-- <div class="col-2 d-flex justify-content-start align-items-start">-->
<!-- <div class="position-relative" :class="getDateStyle(termin)">-->
<!-- <VueDatePicker-->
<!-- v-model="termin.datum"-->
<!-- :clearable="false"-->
<!-- :disabled="true"-->
<!-- :enable-time-picker="false"-->
<!-- :format="formatDate"-->
<!-- :text-input="true"-->
<!-- auto-apply>-->
<!-- </VueDatePicker>-->
<!-- <i class="position-absolute abgabe-zieldatum-overlay fa-solid fa-2x" :class="getDateStyle(termin, 'icon')"></i>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-1 d-flex justify-content-start align-items-start">{{ termin.bezeichnung }}</div>-->
<!-- <div v-if="qualityGateTerminAvailable || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-start align-items-start">-->
<!-- {{ getTerminNoteBezeichnung(termin) }}-->
<!-- </div>-->
<!-- <div v-if="qualityGateTerminAvailable || termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-center align-items-start">-->
<!-- <Checkbox -->
<!-- v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'"-->
<!-- disabled-->
<!-- v-model="termin.upload_allowed"-->
<!-- :binary="true" -->
<!-- :pt="{ root: { class: 'ml-auto' }}"-->
<!-- >-->
<!-- </Checkbox>-->
<!-- </div>-->
<!-- <div class="col-2 d-flex justify-content-start align-items-start">-->
<!-- <Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" cols="45" :disabled="true"></Textarea>-->
<!-- </div>-->
<!-- <div class="col-1 d-flex justify-content-start align-items-center">-->
<!-- {{ termin.abgabedatum?.split("-").reverse().join(".") }}-->
<!-- <a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">-->
<!-- <i class="fa-solid fa-2x fa-file-pdf"></i>-->
<!-- </a>-->
<!-- </div>-->
<!-- <div class="col-3" v-if="!viewMode">-->
<!-- <div class="row">-->
<!-- <div class="col-8">-->
<!-- <Upload v-if="termin && termin.allowedToUpload" accept=".pdf" v-model="termin.file"></Upload>-->
<!-- </div>-->
<!-- <div class="col-4">-->
<!-- <button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload">-->
<!-- {{$p.t('abgabetool/c4upload')}}-->
<!-- <i class="fa-solid fa-upload"></i>-->
<!-- </button>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
<bs-modal ref="modalContainerEnduploadZusatzdaten" class="bootstrap-prompt" dialogClass="modal-lg">
@@ -12,6 +12,7 @@ export const AbgabetoolStudent = {
AbgabeDetail,
VerticalSplit
},
inject: ['isMobile'],
provide() {
return {
notenOptions: Vue.computed(() => this.notenOptions)
@@ -41,6 +42,8 @@ export const AbgabetoolStudent = {
selectedProjektarbeit: null,
tableBuiltResolve: null,
tableBuiltPromise: null,
dataProcessedPromise: null,
dataProcessedResolve: null,
abgabeTableOptions: {
minHeight: 250,
index: 'projektarbeit_id',
@@ -103,12 +106,20 @@ export const AbgabetoolStudent = {
],
persistence: false,
},
abgabeTableEventHandlers: [{
abgabeTableEventHandlers: [
{
event: "tableBuilt",
handler: async () => {
this.tableBuiltResolve()
}
},
{
event: "dataProcessed",
handler: async () => {
console.log('dataProcessed event')
this.dataProcessedResolve()
}
},
{
event: "cellClick",
handler: async (e, cell) => {
@@ -218,6 +229,10 @@ export const AbgabetoolStudent = {
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
dataResolve(resolve) {
console.log('dataResolve')
this.dataProcessedResolve = resolve
},
buildMailToLink(projekt) {
if(projekt.mitarbeiter_uid) { // standard
return 'mailto:' + projekt.mitarbeiter_uid +'@'+ this.domain
@@ -228,7 +243,7 @@ export const AbgabetoolStudent = {
buildBetreuer(abgabe) {
return abgabe.betreuerart_beschreibung + ': ' + (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
},
setupData(data){
async setupData(data){
this.projektarbeiten = data[0]
this.domain = data[1]
this.student_uid = data[2]
@@ -259,13 +274,23 @@ export const AbgabetoolStudent = {
}
})
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
// this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.$refs.abgabeTable.tabulator.setData(d);
await this.dataProcessedPromise
Vue.nextTick(()=>{
this.$refs.abgabeTable?.tabulator.setColumns(this.$refs.abgabeTable?.tabulator.getColumnDefinitions())
})
// TODO: proper event handling cleanup
// todo in general fix this nasty race condition
const t = this.$refs.abgabeTable.tabulator;
t.on("renderComplete", () => {
// only if container width is small enough to trigger collapse
if (t.element.offsetWidth < 600 || this.isMobile) {
t.setColumns(t.getColumnDefinitions());
t.redraw(true)
}
});
},
loadProjektarbeiten() {
this.$api.call(ApiAbgabe.getStudentProjektarbeiten(this.student_uid_prop || this.viewData?.uid || null))
@@ -295,8 +320,9 @@ export const AbgabetoolStudent = {
},
async setupMounted() {
this.tableBuiltPromise = new Promise(this.tableResolve)
this.dataProcessedPromise = new Promise(this.dataResolve)
await this.tableBuiltPromise
// this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.loadProjektarbeiten()
@@ -338,7 +364,7 @@ export const AbgabetoolStudent = {
dialogClass="modal-fullscreen">
<template v-slot:title>
<div>
abgabe detail wtb phrasen
abgabe detail todo phrasen
</div>
</template>
<template v-slot:default>