avoid loading paabgaben a 2nd time for mitarbeiter; extracted getDateStyleClass from components;

This commit is contained in:
Johann Hoffmann
2026-02-19 17:33:41 +01:00
parent 4724008c2d
commit 4b1a9fe892
7 changed files with 126 additions and 198 deletions
@@ -531,6 +531,14 @@ class Abgabe extends FHCAPI_Controller
$pa->beurteilungLinkNew = $newLink;
$pa->beurteilungLinkOld = $oldLink;
// has previously been retrieved via getStudentProjektabgaben but is fetched in advance to avoid having to reload abgaben
$projektarbeitIsCurrent = false;
$returnFunc = function ($result) use (&$projektarbeitIsCurrent) {
$projektarbeitIsCurrent = $result;
};
Events::trigger('projektarbeit_is_current', $pa->projektarbeit_id, $returnFunc);
$pa->isCurrent = $projektarbeitIsCurrent;
$filterFunc = function($projektabgabe) use ($pa) {
return $projektabgabe->projektarbeit_id == $pa->projektarbeit_id;
};
@@ -1,8 +1,8 @@
import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import { getDateStyleClass } from "./getDateStyleClass.js";
const today = new Date()
export const AbgabeMitarbeiterDetail = {
name: "AbgabeMitarbeiterDetail",
components: {
@@ -125,10 +125,12 @@ export const AbgabeMitarbeiterDetail = {
// only insert new abgabe if we actually created a new one, not when saving/editing existing
if(!existingTerminRes){
newTerminRes.dateStyle = getDateStyleClass(newTerminRes, this.notenOptions)
this.projektarbeit.abgabetermine.push(newTerminRes)
} else {
const noteOptExisting = this.allowedNotenOptions.find(opt => opt.note == existingTerminRes.note)
existingTerminRes.note = noteOptExisting
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
}
this.projektarbeit.abgabetermine.sort((a, b) =>new Date(a.datum) - new Date(b.datum))
@@ -270,40 +272,6 @@ export const AbgabeMitarbeiterDetail = {
window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url)
// this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
},
getDateStyleClass(termin) {
const zone = 'Europe/Vienna';
const today = luxon.DateTime.now().setZone(zone);
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
termin.diffindays = datum.diff(today, 'days').days;
const isLate = abgabedatum && abgabedatum > datum;
// GRADE STATUS
if (termin.note) {
if (termin.note.positiv) return 'bestanden';
return 'nichtbestanden';
}
// ACTION REQUIRED FOR GRADE
if (termin.bezeichnung?.benotbar && datum < today) {
return 'beurteilungerforderlich';
}
// SUBMISSION STATUS
if (termin.upload_allowed) {
if (termin.abgabedatum) {
return isLate ? 'verspaetet' : 'abgegeben';
}
// no submission yet
if (datum < today) return 'verpasst';
if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
}
// GENERIC STATUS
return datum < today ? 'verpasst' : 'standard';
},
openBeurteilungLink(link) {
window.open(link, '_blank')
},
@@ -769,20 +737,29 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<Accordion :multiple="true">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="getDateStyleClass(termin) + '-header'">
<template v-for="termin in this.projektarbeit?.abgabetermine" :key="termin.paabgabe_id">
<AccordionTab :headerClass="termin.dateStyle + '-header'">
<template #header>
<div class="d-flex flex-nowrap align-items-center w-100">
<div class="flex-shrink-0 d-flex align-items-center justify-content-center" style="width: 36px; height: 36px; margin-left: -66px;">
<i v-if="getDateStyleClass(termin) == 'verspaetet'" v-tooltip.right="getTooltipVerspaetet" class="fa-solid fa-triangle-exclamation"></i>
<i v-else-if="getDateStyleClass(termin) == 'verpasst'" v-tooltip.right="getTooltipVerpasst" class="fa-solid fa-calendar-xmark"></i>
<i v-else-if="getDateStyleClass(termin) == 'abzugeben'" v-tooltip.right="getTooltipAbzugeben" class="fa-solid fa-hourglass-half"></i>
<i v-else-if="getDateStyleClass(termin) == 'standard'" v-tooltip.right="getTooltipStandard" class="fa-solid fa-clock"></i>
<i v-else-if="getDateStyleClass(termin) == 'abgegeben'" v-tooltip.right="getTooltipAbgegeben" class="fa-solid fa-paperclip"></i>
<i v-else-if="getDateStyleClass(termin) == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>
<i v-else-if="getDateStyleClass(termin) == 'bestanden'" v-tooltip.right="getTooltipBestanden" class="fa-solid fa-check"></i>
<i v-else-if="getDateStyleClass(termin) == 'nichtbestanden'" v-tooltip.right="getTooltipNichtBestanden" class="fa-solid fa-circle-exclamation"></i>
<!-- <i v-if="getDateStyleClass(termin, notenOptions) == 'verspaetet'" v-tooltip.right="getTooltipVerspaetet" class="fa-solid fa-triangle-exclamation"></i>-->
<!-- <i v-else-if="getDateStyleClass(termin, notenOptions) == 'verpasst'" v-tooltip.right="getTooltipVerpasst" class="fa-solid fa-calendar-xmark"></i>-->
<!-- <i v-else-if="getDateStyleClass(termin, notenOptions) == 'abzugeben'" v-tooltip.right="getTooltipAbzugeben" class="fa-solid fa-hourglass-half"></i>-->
<!-- <i v-else-if="getDateStyleClass(termin, notenOptions) == 'standard'" v-tooltip.right="getTooltipStandard" class="fa-solid fa-clock"></i>-->
<!-- <i v-else-if="getDateStyleClass(termin, notenOptions) == 'abgegeben'" v-tooltip.right="getTooltipAbgegeben" class="fa-solid fa-paperclip"></i>-->
<!-- <i v-else-if="getDateStyleClass(termin, notenOptions) == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>-->
<!-- <i v-else-if="getDateStyleClass(termin, notenOptions) == 'bestanden'" v-tooltip.right="getTooltipBestanden" class="fa-solid fa-check"></i>-->
<!-- <i v-else-if="getDateStyleClass(termin, notenOptions) == 'nichtbestanden'" v-tooltip.right="getTooltipNichtBestanden" class="fa-solid fa-circle-exclamation"></i>-->
<i v-if="termin.dateStyle == 'verspaetet'" v-tooltip.right="getTooltipVerspaetet" class="fa-solid fa-triangle-exclamation"></i>
<i v-else-if="termin.dateStyle == 'verpasst'" v-tooltip.right="getTooltipVerpasst" class="fa-solid fa-calendar-xmark"></i>
<i v-else-if="termin.dateStyle == 'abzugeben'" v-tooltip.right="getTooltipAbzugeben" class="fa-solid fa-hourglass-half"></i>
<i v-else-if="termin.dateStyle == 'standard'" v-tooltip.right="getTooltipStandard" class="fa-solid fa-clock"></i>
<i v-else-if="termin.dateStyle == 'abgegeben'" v-tooltip.right="getTooltipAbgegeben" class="fa-solid fa-paperclip"></i>
<i v-else-if="termin.dateStyle == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>
<i v-else-if="termin.dateStyle == 'bestanden'" v-tooltip.right="getTooltipBestanden" class="fa-solid fa-check"></i>
<i v-else-if="termin.dateStyle == 'nichtbestanden'" v-tooltip.right="getTooltipNichtBestanden" class="fa-solid fa-circle-exclamation"></i>
</div>
@@ -344,7 +344,7 @@ export const AbgabeStudentDetail = {
</div>
<Accordion :multiple="true">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<template v-for="termin in this.projektarbeit?.abgabetermine" :key="termin.paabgabe_id">
<AccordionTab :headerClass="termin.dateStyle + '-header'">
<template #header>
<div class="d-flex flex-nowrap align-items-center w-100">
@@ -8,6 +8,7 @@ import ApiStudiensemester from '../../../api/factory/studiensemester.js';
import AbgabeterminStatusLegende from "./StatusLegende.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { splitMailsHelper } from "../../../helpers/EmailHelpers.js"
import { getDateStyleClass} from "./getDateStyleClass.js";
export const AbgabetoolAssistenz = {
name: "AbgabetoolAssistenz",
@@ -381,10 +382,11 @@ export const AbgabetoolAssistenz = {
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
// while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = this.getDateStyleClass(termin)
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
const date = luxon.DateTime.fromISO(termin.datum).endOf('day')
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
@@ -720,7 +722,6 @@ export const AbgabetoolAssistenz = {
return nowInVienna > deadline;
},
setDetailComponent(details){
const pa = this.projektarbeiten.find(projektarbeit => projektarbeit.projektarbeit_id == details.projektarbeit_id)
if(pa?.abgabetermine?.length) {
@@ -750,9 +751,7 @@ export const AbgabetoolAssistenz = {
// assistenz are not allowed to delete deadlines with existing submissions
termin.allowedToDelete = paIsBenotet ? false : !termin.abgabedatum
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
})
const vorname = pa.vorname ?? pa.student_vorname
@@ -760,43 +759,8 @@ export const AbgabetoolAssistenz = {
pa.student = `${vorname} ${nachname}`
this.selectedProjektarbeit = pa
this.$refs.modalContainerAbgabeDetail.show()
},
getDateStyleClass(termin) {
const zone = 'Europe/Vienna';
const today = luxon.DateTime.now().setZone(zone);
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
termin.diffindays = datum.diff(today, 'days').days;
const isLate = abgabedatum && abgabedatum > datum;
// GRADE STATUS
if (termin.note) {
if (termin.note.positiv) return 'bestanden';
return 'nichtbestanden';
}
// ACTION REQUIRED FOR GRADE
if (termin.bezeichnung?.benotbar && datum < today) {
return 'beurteilungerforderlich';
}
// SUBMISSION STATUS
if (termin.upload_allowed) {
if (termin.abgabedatum) {
return isLate ? 'verspaetet' : 'abgegeben';
}
// no submission yet
if (datum < today) return 'verpasst';
if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
}
// GENERIC STATUS
return datum < today ? 'verpasst' : 'standard';
},
openTimeline(val) {
const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id)
if(!projekt) {
@@ -867,7 +831,7 @@ export const AbgabetoolAssistenz = {
case 'abgegeben':
icon = '<i class="fa-solid fa-paperclip"></i>'
break
case 'beurteilungerfolderlich':
case 'beurteilungerforderlich':
icon = '<i class="fa-solid fa-list-check"></i>'
break
case 'bestanden':
@@ -4,6 +4,7 @@ import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { getDateStyleClass } from "./getDateStyleClass.js";
export const AbgabetoolMitarbeiter = {
name: "AbgabetoolMitarbeiter",
@@ -366,7 +367,7 @@ export const AbgabetoolMitarbeiter = {
projekt.abgabetermine.forEach(termin => {
// while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = this.getDateStyleClass(termin)
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
const date = luxon.DateTime.fromISO(termin.datum).endOf('day')
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
@@ -389,40 +390,6 @@ export const AbgabetoolMitarbeiter = {
// seperate check for quality gates
this.checkQualityGateStatus(projekt)
},
getDateStyleClass(termin) {
const zone = 'Europe/Vienna';
const today = luxon.DateTime.now().setZone(zone);
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
termin.diffindays = datum.diff(today, 'days').days;
const isLate = abgabedatum && abgabedatum > datum;
// GRADE STATUS
if (termin.note) {
if (termin.note.positiv) return 'bestanden';
return 'nichtbestanden';
}
// ACTION REQUIRED FOR GRADE
if (termin.bezeichnung?.benotbar && datum < today) {
return 'beurteilungerforderlich';
}
// SUBMISSION STATUS
if (termin.upload_allowed) {
if (termin.abgabedatum) {
return isLate ? 'verspaetet' : 'abgegeben';
}
// no submission yet
if (datum < today) return 'verpasst';
if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
}
// GENERIC STATUS
return datum < today ? 'verpasst' : 'standard';
},
abgabterminFormatter(cell) {
const val = cell.getValue()
@@ -444,7 +411,7 @@ export const AbgabetoolMitarbeiter = {
case 'abgegeben':
icon = '<i class="fa-solid fa-paperclip"></i>'
break
case 'beurteilungerfolderlich':
case 'beurteilungerforderlich':
icon = '<i class="fa-solid fa-list-check"></i>'
break
case 'bestanden':
@@ -607,44 +574,57 @@ export const AbgabetoolMitarbeiter = {
},
setDetailComponent(details){
this.loading=true
this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data[0].retval
pa.isCurrent = res.data[1]
let paIsBenotet = false
if(pa.note !== undefined && pa.note !== null) {
// check if the note is not defined as a non final projektarbeit note
const opt = this.notenOptionsNonFinal.find(opt => opt.note)
// if thats the case allow further work
if(opt) paIsBenotet = false
// else the PA is to be considered finished
paIsBenotet = true
}
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine.forEach(termin => {
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
termin.file = []
// update 08-01-2026: everybody is allowed to do everything in client, critical checks happen at backend level
// termin.allowedToSave = true
// update 21-01-2026: actually blocking operations on finished projektarbeiten seems like a decent idea
termin.allowedToSave = paIsBenotet ? false : true
// lektoren are not allowed to delete deadlines with existing submissions
termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
let paIsBenotet = false
if(pa.note !== undefined && pa.note !== null) {
// check if the note is not defined as a non final projektarbeit note
const opt = this.notenOptionsNonFinal.find(opt => opt.note)
// if thats the case allow further work
if(opt) paIsBenotet = false
// else the PA is to be considered finished
paIsBenotet = true
}
})
pa.student_uid = details.student_uid
pa.student = `${pa.vorname} ${pa.nachname}`
if(pa?.abgabetermine?.length) {
this.$api.call(ApiAbgabe.getSignaturStatusForProjektarbeitAbgaben(pa.abgabetermine.map(termin => termin.paabgabe_id), pa.student_uid))
.then(res => {
if(res.meta.status === 'success') {
res.data.forEach(paabgabe => {
const termin = pa.abgabetermine.find(abgabe => abgabe.paabgabe_id == paabgabe.paabgabe_id)
if(termin && paabgabe.signatur !== undefined) termin.signatur = paabgabe.signatur
})
}
})
}
pa.abgabetermine.forEach(termin => {
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
termin.file = []
this.selectedProjektarbeit = pa
this.$refs.modalContainerAbgabeDetail.show()
// update 08-01-2026: everybody is allowed to do everything in client, critical checks happen at backend level
// termin.allowedToSave = true
// update 21-01-2026: actually blocking operations on finished projektarbeiten seems like a decent idea
termin.allowedToSave = paIsBenotet ? false : true
// lektoren are not allowed to delete deadlines with existing submissions
termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
})
pa.student_uid = details.student_uid
pa.student = `${pa.vorname} ${pa.nachname}`
this.selectedProjektarbeit = pa
this.$refs.modalContainerAbgabeDetail.show()
this.loading = false
}).finally(()=>{this.loading = false})
},
centeredTextFormatter(cell) {
const val = cell.getValue()
@@ -2,8 +2,8 @@ import AbgabeDetail from "./AbgabeStudentDetail.js";
import ApiAbgabe from '../../../api/factory/abgabe.js'
import BsModal from "../../Bootstrap/Modal.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { getDateStyleClass} from "./getDateStyleClass.js";
const today = new Date()
export const AbgabetoolStudent = {
name: "AbgabetoolStudent",
components: {
@@ -48,44 +48,6 @@ export const AbgabetoolStudent = {
};
},
methods: {
getDateStyleClass(termin) {
const zone = 'Europe/Vienna';
const today = luxon.DateTime.now().setZone(zone);
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
termin.diffindays = datum.diff(today, 'days').days;
const isLate = abgabedatum && abgabedatum > datum;
// GRADE STATUS
if (termin.note) {
if(Number.isInteger(termin.note)) {
const opt = this.notenOptions.find(opt => opt.note == termin.note)
if(opt.positiv) return 'bestanden'
}
if (termin.note.positiv) return 'bestanden';
return 'nichtbestanden';
}
// ACTION REQUIRED FOR GRADE
if (termin.bezeichnung?.benotbar && datum < today) {
return 'beurteilungerforderlich';
}
// SUBMISSION STATUS
if (termin.upload_allowed) {
if (termin.abgabedatum) {
return isLate ? 'verspaetet' : 'abgegeben';
}
// no submission yet
if (datum < today) return 'verpasst';
if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
}
// GENERIC STATUS
return datum < today ? 'verpasst' : 'standard';
},
checkQualityGatesStrict(termine) {
let qgate1Passed = false
let qgate2Passed = false
@@ -178,7 +140,7 @@ export const AbgabetoolStudent = {
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
termin.dateStyle = this.getDateStyleClass(termin)
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
})
pa.betreuer = this.buildBetreuer(pa)
@@ -368,7 +330,7 @@ export const AbgabetoolStudent = {
</div>
<Accordion :multiple="true" :activeIndex="activeTabIndex">
<template v-for="projektarbeit in projektarbeiten">
<template v-for="projektarbeit in projektarbeiten" :key="projektarbeit.projektarbeit_id">
<AccordionTab>
<template #header>
@@ -0,0 +1,37 @@
const zone = 'Europe/Vienna';
const today = luxon.DateTime.now().setZone(zone);
export function getDateStyleClass(termin, notenOptions) {
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
termin.diffindays = datum.diff(today, 'days').days;
const isLate = abgabedatum && abgabedatum > datum;
// GRADE STATUS
if (termin.note) {
const opt = typeof termin.note === 'object' ? termin.note : notenOptions.find(nopt => nopt.note == termin.note)
if (opt?.positiv === true) return 'bestanden';
else if (opt?.positiv === false) return 'nichtbestanden';
}
// ACTION REQUIRED FOR GRADE
if (termin.bezeichnung?.benotbar && datum <= today) {
return 'beurteilungerforderlich';
}
// SUBMISSION STATUS
if (termin.upload_allowed) {
if (termin.abgabedatum) {
return isLate ? 'verspaetet' : 'abgegeben';
}
// no submission yet
if (datum < today) return 'verpasst';
if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
}
// GENERIC STATUS
return datum < today ? 'verpasst' : 'standard';
}