fixtermin -> "kein neichreichen erlaubt" phrase/bool logic change; signatur message on enduploads fetched from signatur server on every load request; Inplace toggle for further infos in offcanvas timeline & student details on mobile view(tooltips); reworked col/row structure on detail views for mobile;

This commit is contained in:
Johann Hoffmann
2025-11-11 13:44:47 +01:00
parent cef0046acd
commit 410f0c4b6a
10 changed files with 528 additions and 149 deletions
@@ -45,6 +45,7 @@ class Abgabe extends FHCAPI_Controller
]);
$this->load->library('PhrasesLib');
$this->load->library('SignatureLib');
// Loads LogLib with different debug trace levels to get data of the job that extends this class
// It also specify parameters to set database fields
@@ -113,6 +114,10 @@ class Abgabe extends FHCAPI_Controller
$ret = $this->ProjektarbeitModel->getProjektarbeitAbgabetermine($projektarbeit_id);
foreach($ret->retval as $termin) {
$this->checkAbgabeSignatur($termin, $projektarbeit);
}
$this->terminateWithSuccess(array($ret, $paIsCurrent));
}
@@ -134,7 +139,7 @@ class Abgabe extends FHCAPI_Controller
$this->addMeta('isZugeteilterBetreuer', $isZugeteilterBetreuer);
$isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID());
if ($isMitarbeiter && $isZugeteilterBetreuer){
if ($isMitarbeiter) {
$result = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer($uid);
} else {
$result = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer(getAuthUID());
@@ -152,7 +157,7 @@ class Abgabe extends FHCAPI_Controller
}
if($pa->zweitbetreuer_person_id !== null) {
// TODO: dont have to wait for 2038, see assistenz query in projektarbeit_model
// TODO: see assistenz query in projektarbeit_model
// zweitbetreuer info since the 'getStudentProjektarbeitenWithBetreuer' query got quiete large,
// enjoy optimizing that one in 2038. we need this to render a string like
@@ -165,8 +170,6 @@ class Abgabe extends FHCAPI_Controller
$pa->zweitbetreuer = $data[0];
}
}
}
}
@@ -206,7 +209,8 @@ class Abgabe extends FHCAPI_Controller
'updateamum' => date('Y-m-d H:i:s')
));
$this->sendUploadEmail($bperson_id, $projektarbeit_id, $paabgabetyp_kurzbz, $student_uid);
// TODO: consider this for daily abgabetool email job
// $this->sendUploadEmail($bperson_id, $projektarbeit_id, $paabgabetyp_kurzbz, $student_uid);
$this->logLib->logInfoDB(array('zwischenupload',$res, array(
'abgabedatum' => date('Y-m-d'),
@@ -256,48 +260,53 @@ class Abgabe extends FHCAPI_Controller
if(file_exists(PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf')) {
// Loads Libraries
$this->load->library('SignatureLib');
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
// Check if the document is signed
$signaturVorhanden = true;
$signList = SignatureLib::list(PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf');
if (is_array($signList) && count($signList) > 0)
{
// The document is signed
$uploadedDocumentSigned = 'The document is signed';
}
elseif ($signList === null)
{
$uploadedDocumentSigned = 'WARNING: signature server error';
}
else
{
$signaturVorhanden = false;
$uploadedDocumentSigned = 'No document signature found';
}
$this->addMeta('signaturInfo', $uploadedDocumentSigned);
$result = $this->ProjektarbeitModel->load($projektarbeit_id);
$projektarbeitArr = $this->getDataOrTerminateWithError($result);
if ($signaturVorhanden === false)
if(count($projektarbeitArr) > 0) {
$projektarbeit = $projektarbeitArr[0];
} else {
$this->terminateWithError($this->p->t('global','projektarbeitNichtGefunden'), 'general');
}
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$result = $this->PaabgabeModel->load($paabgabe_id);
$paabgabeArr = $this->getDataOrTerminateWithError($result);
if(count($paabgabeArr) > 0) {
$paabgabe = $paabgabeArr[0];
} else {
$this->terminateWithError($this->p->t('global','projektabgabeNichtGefunden'), 'general');
}
$this->checkAbgabeSignatur($paabgabe, $projektarbeit);
$signaturstatus = $paabgabe->signatur;
if ($paabgabe->signatur === false)
{
$this->signaturFehltEmail($student_uid);
}
// update projektarbeit cols
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$this->ProjektarbeitModel->updateProjektarbeit($projektarbeit_id,$sprache,$abstract,$abstract_en
,$schlagwoerter, $schlagwoerter_en, $seitenanzahl);
// update paabgabe datum
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$res = $this->PaabgabeModel->update($paabgabe_id, array(
'abgabedatum' => date('Y-m-d'),
'updatevon' => getAuthUID(),
'updateamum' => date('Y-m-d H:i:s')
));
$this->sendUploadEmail($bperson_id, $projektarbeit_id, $paabgabetyp_kurzbz, $student_uid);
$res = $this->PaabgabeModel->load($res->retval);
$abgabe = getData($res)[0];
$abgabe->signatur = $signaturstatus;
// TODO: consider for email job
// $this->sendUploadEmail($bperson_id, $projektarbeit_id, $paabgabetyp_kurzbz, $student_uid);
$this->logLib->logInfoDB(array('endupload',$res, array(
'abgabedatum' => date('Y-m-d'),
@@ -306,7 +315,7 @@ class Abgabe extends FHCAPI_Controller
), getAuthUID(), getAuthPersonId(), array($projektarbeit_id,$sprache,$abstract,$abstract_en
,$schlagwoerter, $schlagwoerter_en, $seitenanzahl)));
$this->terminateWithSuccess($res);
$this->terminateWithSuccess($abgabe);
} else {
$this->terminateWithError('Error moving File');
}
@@ -436,12 +445,12 @@ class Abgabe extends FHCAPI_Controller
if ($zweitbegutachterRes)
{
$zweitbegutachterResults = $zweitbegutachterRes->retval;
$zweitbegutachterResults = getData($zweitbegutachterRes->retval)[0];
foreach ($zweitbegutachterResults as $begutachter)
{
// token generieren, wenn noch nicht vorhanden und notwendig (wird in methode überprüft)
$tokenGenRes = $zweitbegutachter->generateZweitbegutachterToken($begutachter->person_id, $projektarbeit_id);
$tokenGenRes = $this->ProjektbetreuerModel->generateZweitbegutachterToken($begutachter->person_id, $projektarbeit_id);
if (!$tokenGenRes)
{
@@ -870,6 +879,7 @@ class Abgabe extends FHCAPI_Controller
if (!isset($studiengang_kz) || isEmptyString($studiengang_kz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$result = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz, $benotet);
$projektarbeiten = $this->getDataOrTerminateWithError($result);
@@ -886,15 +896,17 @@ class Abgabe extends FHCAPI_Controller
$projektabgaben = $this->getDataOrTerminateWithError($ret);
// map the abgaben into projektarbeiten
foreach($projektarbeiten as $projektarbeit) {
$filterFunc = function($projektabgabe) use ($projektarbeit) {
return $projektabgabe->projektarbeit_id == $projektarbeit->projektarbeit_id;
};
$projektarbeit->abgabetermine = array_values(array_filter($projektabgaben, $filterFunc));
// check the signature status for enduploads
foreach($projektarbeit->abgabetermine as $abgabe) {
$this->checkAbgabeSignatur($abgabe, $projektarbeit);
}
}
$this->terminateWithSuccess(array($projektarbeiten, DOMAIN));
@@ -935,18 +947,10 @@ class Abgabe extends FHCAPI_Controller
if(getAuthUID() == $student_uid || $isZugeteilterBetreuer || $isAssistenz) {
$file_path = PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf';
if(file_exists($file_path)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Disposition: attachment; filename="'.basename($file_path).'"');
header('Content-Length: ' . filesize($file_path));
flush(); // send headers first just in case
readfile($file_path); // read file content to output buffer
$this->terminateWithFileOutput('application/octet-stream', filesize($file_path), basename($file_path));
} else {
$this->terminateWithError('File not found');
@@ -955,5 +959,36 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithError('Keine Zuordnung!');
}
}
private function checkAbgabeSignatur($abgabe, $projektarbeit) {
if($abgabe->paabgabetyp_kurzbz != 'end') {
return;
}
$path = PAABGABE_PATH.$abgabe->paabgabe_id.'_'.$projektarbeit->student_uid.'.pdf';
$signaturVorhanden = null; // if frontend receives null -> indicates no file found at path
if(file_exists($path)) {
// Check if the document is signed
$signList = SignatureLib::list($path);
if (is_array($signList) && count($signList) > 0)
{
// The document is signed
$signaturVorhanden = true;
}
elseif ($signList === null)
{
// frontend knows to handle it this way for signatures
$signaturVorhanden = 'error';
}
else
{
$signaturVorhanden = false;
}
$abgabe->signatur = $signaturVorhanden;
}
}
}
+1
View File
@@ -26,6 +26,7 @@ $includesArray = array(
'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/npm-asset/primevue/timeline/timeline.min.js',
'vendor/npm-asset/primevue/inplace/inplace.min.js',
'vendor/npm-asset/primevue/message/message.min.js',
'vendor/moment/luxonjs/luxon.min.js'
),
'customJSModules' => array(
@@ -34,6 +34,7 @@ $includesArray = array(
'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/npm-asset/primevue/timeline/timeline.min.js',
'vendor/npm-asset/primevue/inplace/inplace.min.js',
'vendor/npm-asset/primevue/message/message.min.js',
'vendor/moment/luxonjs/luxon.min.js'
),
'customJSModules' => array(
-5
View File
@@ -35,11 +35,6 @@ export default {
config: {Headers: { "Content-Type": "multipart/form-data" }}
};
},
getStudentProjektarbeitAbgabeFile(paabgabe_id, student_uid) {
const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${paabgabe_id}&student_uid=${student_uid}`;
window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url)
},
getMitarbeiterProjektarbeiten(all) {
return {
method: 'get',
@@ -14,6 +14,7 @@ export const AbgabeMitarbeiterDetail = {
SpeedDial: primevue.speeddial,
Accordion: primevue.accordion,
AccordionTab: primevue.accordiontab,
Message: primevue.message,
VueDatePicker
},
inject: [
@@ -74,6 +75,9 @@ export const AbgabeMitarbeiterDetail = {
const paabgabe_id = termin.paabgabe_id
termin.note_pk = termin.note?.note ?? null
termin.betreuer_person_id = this.projektarbeit.betreuer_person_id
// phrasentext 'no late submission allowed' to 'late submission allowed' + boolean UI invert
termin.fixtermin = !termin.invertedFixtermin
return this.$api.call(ApiAbgabe.postProjektarbeitAbgabe(termin)).then( (res) => {
if(res?.meta?.status == 'success') {
this.$fhcAlert.alertSuccess(this.$p.t('ui/gespeichert'))
@@ -121,6 +125,7 @@ export const AbgabeMitarbeiterDetail = {
'paabgabe_id': -1,
'projektarbeit_id': this.projektarbeit.projektarbeit_id,
'fixtermin': false,
'inertedFixtermin': true,
'kurzbz': '', // todo kurzbz textfield value vorschlag für qualgates
'datum': new Date().toISOString().split('T')[0],
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
@@ -177,7 +182,10 @@ export const AbgabeMitarbeiterDetail = {
return true;
},
downloadAbgabe(termin) {
this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${termin.paabgabe_id}&student_uid=${this.projektarbeit.student_uid}`;
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))
},
dateDiffInDays(datum, today){
const oneDayMs = 1000 * 60 * 60 * 24
@@ -258,6 +266,7 @@ export const AbgabeMitarbeiterDetail = {
'paabgabe_id': -1,
'projektarbeit_id': this.projektarbeit.projektarbeit_id,
'fixtermin': false,
'invertedFixtermin': true,
'kurzbz': '',
'datum': new Date().toISOString().split('T')[0],
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
@@ -292,6 +301,7 @@ export const AbgabeMitarbeiterDetail = {
'paabgabe_id': -1,
'projektarbeit_id': this.projektarbeit.projektarbeit_id,
'fixtermin': false,
'invertedFixtermin': true,
'kurzbz': '',
'datum': new Date().toISOString().split('T')[0],
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
@@ -314,6 +324,21 @@ export const AbgabeMitarbeiterDetail = {
},
computed: {
getMessagePtStyle() {
// adjust outer spacing and internal padding to appear similar to doenload button in size
return {
root: {
style: {
margin: '0px'
}
},
wrapper: {
style: {
padding: '6px'
}
}
}
},
getActiveIndexTabArray() {
// here we try to assume which abgabetermine are the most relevant to the current user
@@ -397,6 +422,12 @@ export const AbgabeMitarbeiterDetail = {
class: "custom-tooltip"
}
},
getTooltipAbgabeDetected() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgabeDetected')),
class: "custom-tooltip"
}
},
getTooltipNotAllowedToSave() {
return {
value: this.$p.t('abgabetool/c4notAllowedToEditAbgabeTermin'),
@@ -430,7 +461,12 @@ export const AbgabeMitarbeiterDetail = {
}
this.newTermin.upload_allowed = newVal.upload_allowed_default
}
},
'projektarbeit'(newVal) {
// set invertedFixtermin field for UI/UX purposes -> avoid double negation in text
newVal?.abgabetermine?.forEach(termin => termin.invertedFixtermin = !termin.fixtermin)
}
},
created() {
@@ -454,20 +490,18 @@ export const AbgabeMitarbeiterDetail = {
</template>
<template v-slot:default>
<div v-if="showAutomagicModalPhrase" class="text-center"><p>{{$p.t('abgabetool/c4abgabeQualGateNegativAddNewAutomagisch')}}</p></div>
<!-- minheight to avoid z-index magic for the datepicker inside the modal inside the modal...-->
<div v-if="newTermin">
<!-- fixtermin is not an option for lektors-->
<!-- <div class="row">-->
<!-- <div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixtermin')}}</div>-->
<!-- <div class="col-8 col-md-9">-->
<!-- <Checkbox -->
<!-- v-model="newTermin.fixtermin"-->
<!-- :binary="true" -->
<!-- :pt="{ root: { class: 'ml-auto' }}"-->
<!-- >-->
<!-- </Checkbox>-->
<!-- </div>-->
<!-- </div>-->
<div v-if="assistenzMode" class="row">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-8 col-md-9">
<Checkbox
v-model="newTermin.invertedFixtermin"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{ $capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="col-8 col-md-9">
@@ -586,13 +620,16 @@ export const AbgabeMitarbeiterDetail = {
<div v-if="termin?.fixtermin" class="col-auto" style="transform: translateX(-30px)">
<i v-tooltip.right="getTooltipFixtermin" class="fa-solid fa-lock"></i>
</div>
<div v-if="termin?.abgabedatum" class="col-auto" style="transform: translateX(-30px)">
<i v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
</div>
</template>
<div class="row mt-2" v-if="assistenzMode">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixterminv3')}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4fixterminv4') )}}</div>
<div class="col-12 col-md-9">
<Checkbox
v-model="termin.fixtermin"
v-model="termin.invertedFixtermin"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
@@ -600,8 +637,8 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="col-12 col-md-9">
<VueDatePicker
v-model="termin.datum"
:clearable="false"
@@ -614,8 +651,8 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}</div>
<div class="col-12 col-md-9">
<Dropdown
:style="{'width': '100%'}"
:disabled="!termin.allowedToSave"
@@ -629,8 +666,8 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<div class="row mt-2" v-if="termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4upload_allowed') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4upload_allowed') )}}</div>
<div class="col-12 col-md-9">
<Checkbox
v-model="termin.upload_allowed"
:binary="true"
@@ -640,8 +677,8 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<div class="row mt-2" v-if="termin.bezeichnung?.benotbar">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4note') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4note') )}}</div>
<div class="col-12 col-md-9">
<Dropdown
:style="{'width': '100%'}"
v-model="termin.note"
@@ -651,21 +688,21 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<div class="row mt-2" v-if="termin.bezeichnung?.benotbar">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" class="w-100" :disabled="!termin.allowedToSave"></Textarea>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" class="w-100" rows="1" :disabled="!termin.allowedToSave"></Textarea>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabedatum') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabedatum') )}}</div>
<div class="col-12 col-md-9">
<template v-if="termin?.abgabedatum">
<div class="row">
<div style="width:250px">
@@ -682,6 +719,16 @@ export const AbgabeMitarbeiterDetail = {
<a> {{$capitalize( $p.t('abgabetool/c4downloadAbgabe') )}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
</div>
<div v-if="termin?.signatur !== undefined && termin?.signatur !== null" class="col-auto">
<Message v-if="termin?.signatur == true" severity="success" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturGefunden') }} </Message>
<Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4keineSignatur') }} </Message>
<Message v-else-if="termin?.signatur == 'error'" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturServerError') }} </Message>
</div>
<div v-else class="col-auto">
<Message severity="info" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4noFileFound') }} </Message>
</div>
</div>
</template>
<template v-else>
@@ -690,10 +737,10 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">
<!-- TODO: row description? -->
<div class="col-12 col-md-3 fw-bold">
{{ $capitalize( $p.t('abgabetool/c4actions') )}}
</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-9">
<div class="row">
<div class="col-auto">
<button v-if="termin.allowedToSave" style="max-height: 40px;" class="btn btn-primary border-0" @click="saveTermin(termin)">
@@ -15,6 +15,8 @@ export const AbgabeStudentDetail = {
Textarea: primevue.textarea,
Accordion: primevue.accordion,
AccordionTab: primevue.accordiontab,
Message: primevue.message,
Inplace: primevue.inplace,
VueDatePicker
},
inject: ['notenOptions', 'isMobile', 'isViewMode'],
@@ -144,7 +146,10 @@ export const AbgabeStudentDetail = {
this.$refs.modalContainerEnduploadZusatzdaten.hide()
},
downloadAbgabe(termin) {
this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${termin.paabgabe_id}&student_uid=${this.projektarbeit.student_uid}`;
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))
},
formatDate(dateParam, showTime = true) {
const date = new Date(dateParam)
@@ -196,6 +201,9 @@ export const AbgabeStudentDetail = {
// update 'abgabedatum' for successful upload -> shows the pdf icon and date once set
termin.abgabedatum = new Date().toISOString().split('T')[0];
if(res?.data?.signatur !== undefined) {
termin.signatur = res.data.signatur
}
} else {
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4fileUploadErrorv3')))
@@ -215,6 +223,7 @@ export const AbgabeStudentDetail = {
const datum = new Date(termin.datum)
const abgabedatum = new Date(termin.abgabedatum)
// avoid renaming these statuses as their names are used as css keys
// https://wiki.fhcomplete.info/doku.php?id=cis:abgabetool_fuer_studierende
if (termin.abgabedatum === null) {
if(datum < today) {
@@ -259,9 +268,29 @@ export const AbgabeStudentDetail = {
this.form.schlagwoerter_en = newVal.schlagwoerter_en ?? ''
this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter ?? ''
this.form.seitenanzahl = newVal.seitenanzahl ?? 1
// get dateStyles for each abgabetermin
newVal?.abgabetermine?.forEach(termin => {
termin.dateStyle = this.getDateStyleClass(termin)
})
}
},
computed: {
getMessagePtStyle() {
// adjust outer spacing and internal padding to appear similar to doenload button in size
return {
root: {
style: {
margin: '0px'
}
},
wrapper: {
style: {
padding: '6px'
}
}
}
},
getActiveIndexTabArray() {
// here we try to do mind reading logic by assuming which abgabetermine are the most relevant to the current user
@@ -332,6 +361,12 @@ export const AbgabeStudentDetail = {
class: "custom-tooltip"
}
},
getTooltipAbgabeDetected() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgabeDetected')),
class: "custom-tooltip"
}
},
getTooltipNotAllowedToUpload() {
if(this.isViewMode) {
return {
@@ -367,43 +402,69 @@ export const AbgabeStudentDetail = {
<Accordion :multiple="true" :activeIndex="getActiveIndexTabArray">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="getDateStyleClass(termin) + '-header'">
<AccordionTab :headerClass="termin.dateStyle + '-header'">
<template #header>
<div class="d-flex row w-100">
<div class="d-flex row w-100 flex-nowrap">
<div class="col-auto" style="transform: translateX(-62px)">
<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-check"></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-check"></i>
</div>
<div class="col-auto text-start" style="min-width: max(150px, 20%); max-width: min(300px, 30%); transform: translateX(-30px)">
<div class="col-auto text-start pl-2 pr-0 pt-0 pb-0" style="min-width: max(120px, 20%); max-width: min(300px, 30%); transform: translateX(-30px)">
<span>{{ termin?.bezeichnung }}</span>
</div>
<div class="col-auto text-start" style="min-width: 100px; transform: translateX(-30px)">
<div class="col-auto text-start p-0" style="min-width: max(80px, 15%); transform: translateX(-30px)">
<span>{{ formatDate(termin.datum, false) }}</span>
</div>
<div v-if="termin?.fixtermin" class="col-auto" style="transform: translateX(-30px)">
<i v-tooltip.right="getTooltipFixtermin" class="fa-solid fa-lock"></i>
<div class="col-auto" style="transform: translateX(-30px); min-width: 42px;">
<i v-if="termin?.fixtermin" v-tooltip.right="getTooltipFixtermin" class="fa-solid fa-lock"></i>
<i v-if="termin?.abgabedatum && isMobile" v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
<div v-if="termin?.abgabedatum && !isMobile" class="col-auto" style="transform: translateX(-30px); min-width: 42px;">
<i v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
</div>
</template>
<!-- <div class="row">-->
<!-- <div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixterminv3')}}</div>-->
<!-- <div class="col-8 col-md-9">-->
<!-- <Checkbox -->
<!-- disabled-->
<!-- v-model="termin.fixtermin"-->
<!-- :binary="true" -->
<!-- :pt="{ root: { class: 'ml-auto' }}"-->
<!-- >-->
<!-- </Checkbox>-->
<!-- </div>-->
<!-- </div>-->
<div v-if="isMobile" class="row mt-2 align-items-center">
<Inplace
closable
:closeButtonProps="{
style: {
position: 'relative',
bottom: '100px',
left: '80%',
zIndex: 1
}
}"
>
<template #display>{{ $capitalize($p.t('abgabetool/c4tapForTooltipInfo'))}}</template>
<template #content>
<div class="col-auto">
<div class="row">
<div class="col-12 col-md-3 fw-bold">{{ $capitalize($p.t('abgabetool/c4abgabeZeitstatus')) }}</div>
<div class="col-12 col-md-9">{{$p.t('abgabetool/c4tooltip' + $capitalize(termin?.dateStyle) )}}</div>
</div>
<div class="row">
<div class="col-12 col-md-3 fw-bold">{{ $capitalize($p.t('abgabetool/c4fixterminv4')) }}</div>
<div class="col-12 col-md-9">{{!termin?.fixtermin}}</div>
</div>
<div class="row">
<div class="col-12 col-md-3 fw-bold">{{ $capitalize($p.t('abgabetool/c4fileUploaded')) }}</div>
<div class="col-12 col-md-9">{{termin?.abgabedatum !== null}}</div>
</div>
</div>
</template>
</Inplace>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="col-12 col-md-9">
<VueDatePicker
v-model="termin.datum"
:clearable="false"
@@ -417,15 +478,15 @@ export const AbgabeStudentDetail = {
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabetypv2') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabetypv2') )}}</div>
<div class="col-12 col-md-9">
{{ termin.bezeichnung }}
</div>
</div>
<div class="row mt-2" v-if="termin.note">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4note') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4note') )}}</div>
<div class="col-12 col-md-9">
<div class="col-1 d-flex justify-content-start align-items-start">
{{ getTerminNoteBezeichnung(termin) }}
</div>
@@ -433,37 +494,54 @@ export const AbgabeStudentDetail = {
</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">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" class="w-100" disabled></Textarea>
</div>
</div>
<div v-if="termin.kurzbz && termin.kurzbz.length > 0" class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" class="w-100" :disabled="true"></Textarea>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabedatum') )}}</div>
<div class="col-8 col-md-9">
<template v-if="termin?.abgabedatum">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<button v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" class="btn btn-primary">
<a> {{$capitalize($p.t('abgabetool/c4downloadAbgabe') )}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
</template>
<template v-else>
{{ $capitalize( $p.t('abgabetool/c4nochNichtsAbgegeben') )}}
</template>
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabedatum') )}}</div>
<div class="col-12 col-md-9">
<template v-if="termin?.abgabedatum">
<div class="row">
<div style="width:100px">
<h6>{{ termin.abgabedatum?.split("-").reverse().join(".") }}</h6>
</div>
<div class="col-auto">
<button v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" class="btn btn-primary">
<a> {{$capitalize($p.t('abgabetool/c4downloadAbgabe') )}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
</div>
<template v-if="termin.paabgabetyp_kurzbz == 'end'">
<div v-if="termin?.signatur !== undefined" class="col-auto">
<Message v-if="termin?.signatur == true" severity="success" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturGefunden') }} </Message>
<Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4keineSignatur') }} </Message>
<Message v-else-if="termin?.signatur == null" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturServerError') }} </Message>
</div>
<div v-else class="col-auto">
<Message severity="info" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4noFileFound') }} </Message>
</div>
</template>
</div>
</template>
<template v-else>
{{ $capitalize( $p.t('abgabetool/c4nochNichtsAbgegeben') )}}
</template>
</div>
</div>
<div class="row mt-2" v-if="termin.upload_allowed">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4fileupload') )}}</div>
<div class="col-8 col-md-9">
<div class="col-12 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4fileupload') )}}</div>
<div class="col-12 col-md-9">
<div class="row" v-if="termin?.allowedToUpload">
<div class="col-12 col-sm-6 mb-2">
<Upload
@@ -53,6 +53,7 @@ export const AbgabetoolAssistenz = {
},
data() {
return {
studiensemesterOptions: null,
allSem: null,
curSem: null,
notenOptionFilter: null,
@@ -851,7 +852,7 @@ export const AbgabetoolAssistenz = {
<template v-slot:default>
<div class="row">
<div class="col-1 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4fixterminv3')}}
{{$p.t('abgabetool/c4fixterminv4')}}
</div>
<div class="col-3 d-flex justify-content-center align-items-center">
{{$capitalize($p.t('abgabetool/c4zieldatum'))}}
@@ -928,7 +929,6 @@ export const AbgabetoolAssistenz = {
{{ $p.t('abgabetool/c4projektarbeitTimelineTitle') }}
</template>
<!-- TODO: take care of absolute offset values -->
<div class="row" style="margin-bottom: 12px;">
<Inplace
closable
@@ -945,19 +945,19 @@ export const AbgabetoolAssistenz = {
<template #content>
<div class="col-auto">
<div class="row">
<div class="col-2">Student: </div>
<div class="col-3">Student: </div>
<div class="col-7">{{timelineProjekt?.student_vorname}} {{timelineProjekt?.student_nachname}}</div>
</div>
<div class="row">
<div class="col-2">Uid: </div>
<div class="col-3">Uid: </div>
<div class="col-7">{{timelineProjekt?.student_uid}}</div>
</div>
<div class="row">
<div class="col-2">{{timelineProjekt?.betreuerart}}: </div>
<div class="col-3">{{timelineProjekt?.betreuerart}}: </div>
<div class="col-7">{{timelineProjekt?.erstbetreuer_full_name}}</div>
</div>
<div class="row">
<div class="col-2">Titel: </div>
<div class="col-3">Titel: </div>
<div class="col-7">{{timelineProjekt?.titel}}</div>
</div>
</div>
@@ -74,15 +74,18 @@ export const AbgabetoolStudent = {
pa.abgabetermine.forEach(termin => {
termin.file = []
termin.allowedToUpload = false
// termin.datum = '2025-10-16'
// TODO: change this and corresponding tooltips every time FH decides QG work different now
if(termin.paabgabetyp_kurzbz == 'end') {
// production logic
termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGates(pa.abgabetermine)
// development purposes
// termin.allowedToUpload = this.checkQualityGates(pa.abgabetermine)
} else if(termin.fixtermin) {
termin.allowedToUpload = !this.isPastDate(termin.datum)
} else {
// TODO: this will confuse people so much, by requirement we should NOT show people this flag
// but it still should have an effect?
// this could confuse people since we should dont show people this flag
termin.allowedToUpload = termin.upload_allowed
}
@@ -251,7 +254,6 @@ export const AbgabetoolStudent = {
</template>
<template v-slot:default>
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</template>
</bs-modal>
@@ -268,7 +270,7 @@ export const AbgabetoolStudent = {
<template #header>
<div class="d-flex row w-100">
<div class="col-6 text-start">
<div class="text-start" :class="projektarbeit.note != null ? 'col-6' : 'col-12'">
<span>{{getAccTabHeaderForProjektarbeit(projektarbeit)}}</span>
</div>
<div class="col-6 text-end">
@@ -33,7 +33,7 @@ export const DeadlineOverview = {
placeholder: this.$p.t('global/noDataAvailable'),
columns: [
{title: Vue.computed(() => this.$p.t('abgabetool/c4zieldatum')), field: 'datum', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4fixterminv3')), field: 'fixterminstring', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4fixterminv4')), field: 'fixterminstring', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4abgabetypv2')), field: 'typ_bezeichnung', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4abgabekurzbz')), field: 'kurzbz', formatter: this.centeredTextFormatter, widthGrow: 3},
{title: Vue.computed(() => this.$p.t('person/studentIn')), field: 'student', formatter: this.centeredTextFormatter, widthGrow: 2},
+223 -3
View File
@@ -42120,18 +42120,178 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4fixterminv3',
'phrase' => 'c4fixterminv4',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "kein Nachreichen möglich",
'text' => "Nachreichen möglich",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "no late submission possible",
'text' => "late submission possible",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4orgform',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Organisationseinheit",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "organization unit",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4studstatus',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Studierendenstatus",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "student status",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4signaturGefunden',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Digitale Signatur gefunden",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "digital signature found",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4keineSignatur',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Keine Digitale Signatur gefunden",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "no digital signature found",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4signaturServerError',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Signatur Server Error",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "signature server error",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4noFileFound',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Datei wurde nicht gefunden",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "file not found",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4fileUploaded',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Datei hochgeladen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "file uploaded",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4actions',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Aktionen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "actions",
'description' => '',
'insertvon' => 'system'
)
@@ -43062,6 +43222,66 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4tooltipAbgabeDetected',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Zu diesem Abgabetermin wurde bereits eine Datei hochgeladen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'a file has been uploaded for this deadline',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4tapForTooltipInfo',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Tippen Sie für Tooltip-Informationen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'tap for tooltip information',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4abgabeZeitstatus',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Zeitstatus",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'time status',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',