load projektarbeiten for studiengänge -> assistenz page; speed dial position rework; automagicmodal logic fix; activeTabIndex by date in detail views; tooltips on icons; $capitalize phrasen to ensure capitalization; phrasenpromise & resolve similar to anw; modal component emits fullscreen event now;

This commit is contained in:
Johann Hoffmann
2025-10-27 14:40:56 +01:00
parent 0d2e41cf2f
commit 259c2aec14
19 changed files with 1345 additions and 314 deletions
+1
View File
@@ -65,6 +65,7 @@ $route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1';
$route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1';
$route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1';
$route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz';
$route['Abgabetool/Mitarbeiter'] = 'Cis/Abgabetool/Mitarbeiter';
$route['Abgabetool/Student'] = 'Cis/Abgabetool/Student';
$route['Abgabetool/Student/.*'] = 'Cis/Abgabetool/Student/$1';
+19 -3
View File
@@ -15,9 +15,10 @@ class Abgabetool extends Auth_Controller
parent::__construct([
'index' => self::PERM_LOGGED,
'getStudentProjektarbeitAbgabeFile' => array('basis/abgabe_student:rw', 'basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'Mitarbeiter' => self::PERM_LOGGED,
'Student' => self::PERM_LOGGED,
'Deadlines' => self::PERM_LOGGED
'Mitarbeiter' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'Assistenz' => array('basis/abgabe_assistenz:rw'),
'Student' => array('basis/abgabe_student:rw', 'basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'Deadlines' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw')
]);
}
@@ -29,6 +30,7 @@ class Abgabetool extends Auth_Controller
*/
public function index()
{
// TODO: do we even need this?
$viewData = array(
'uid'=>getAuthUID(),
@@ -71,6 +73,20 @@ class Abgabetool extends Auth_Controller
}
}
public function Assistenz()
{
$viewData = array(
'uid'=>getAuthUID(),
);
if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolAssistenz']);
} else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolAssistenz']);
}
}
public function Deadlines()
{
@@ -38,7 +38,8 @@ class Abgabe extends FHCAPI_Controller
'postSerientermin' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
'fetchDeadlines' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
'getPaAbgabetypen' => self::PERM_LOGGED,
'getNoten' => self::PERM_LOGGED
'getNoten' => self::PERM_LOGGED,
'getProjektarbeitenForStudiengang' =>array('basis/abgabe_assistenz:rw')
]);
$this->load->library('PhrasesLib');
@@ -148,6 +149,9 @@ class Abgabe extends FHCAPI_Controller
$pa->email = $data[0]->private_email;
}
if($pa->zweitbetreuer_person_id !== null) {
// TODO: dont have to wait for 2038, 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
// Zweitbegutachter: FH-Prof. PD DI Dr. techn. Vorname Nachname MBA
@@ -850,4 +854,41 @@ class Abgabe extends FHCAPI_Controller
$subject
);
}
public function getProjektarbeitenForStudiengang() {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$studiengang_kz = $this->input->get("studiengang_kz",TRUE);
if (!isset($studiengang_kz) || isEmptyString($studiengang_kz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
// TODO revert arr return new/old
// $arr = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz);
// $result = $arr[0];
$result = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz);
$projektarbeiten = $this->getDataOrTerminateWithError($result);
$mapFunc = function($projektarbeit) {
return $projektarbeit->projektarbeit_id;
};
$projektarbeiten_ids = array_map($mapFunc, $projektarbeiten);
$ret = $this->ProjektarbeitModel->getProjektarbeitenAbgabetermine($projektarbeiten_ids);
$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));
}
$this->terminateWithSuccess(array($projektarbeiten, DOMAIN));
}
}
@@ -13,7 +13,7 @@ class Paabgabetyp_model extends DB_Model
}
public function getAll() {
$qry = "SELECT * FROM campus.tbl_paabgabetyp";
$qry = "SELECT * FROM campus.tbl_paabgabetyp ORDER BY bezeichnung";
return $this->execReadOnlyQuery($qry);
}
@@ -183,12 +183,32 @@ class Projektarbeit_model extends DB_Model
return $this->execReadOnlyQuery($qry, array($projektarbeit_id));
}
public function getProjektarbeitenAbgabetermine($projektarbeiten_ids) {
$qry ="SELECT campus.tbl_paabgabe.paabgabe_id,
campus.tbl_paabgabe.projektarbeit_id,
campus.tbl_paabgabe.fixtermin,
campus.tbl_paabgabe.kurzbz,
campus.tbl_paabgabe.datum,
campus.tbl_paabgabe.note,
campus.tbl_paabgabe.upload_allowed,
campus.tbl_paabgabe.beurteilungsnotiz,
campus.tbl_paabgabetyp.paabgabetyp_kurzbz,
campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon
FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz)
WHERE campus.tbl_paabgabe.projektarbeit_id IN ?
ORDER BY campus.tbl_paabgabe.datum";
return $this->execReadOnlyQuery($qry, array($projektarbeiten_ids));
}
public function getProjektbetreuerAnrede($bperson_id) {
$qry_betr="SELECT distinct trim(COALESCE(titelpre,'')||' '||COALESCE(vorname,'')||' '||COALESCE(nachname,'')||' '||COALESCE(titelpost,'')) as first,
$qry_betr="SELECT DISTINCT trim(COALESCE(titelpre,'')||' '||COALESCE(vorname,'')||' '||COALESCE(nachname,'')||' '||COALESCE(titelpost,'')) as first,
public.tbl_mitarbeiter.mitarbeiter_uid, anrede
FROM public.tbl_person JOIN lehre.tbl_projektbetreuer ON(lehre.tbl_projektbetreuer.person_id=public.tbl_person.person_id)
JOIN public.tbl_benutzer ON(public.tbl_benutzer.person_id=public.tbl_person.person_id)
JOIN public.tbl_mitarbeiter ON(public.tbl_benutzer.uid=public.tbl_mitarbeiter.mitarbeiter_uid)
LEFT JOIN public.tbl_benutzer ON(public.tbl_benutzer.person_id=public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON(public.tbl_benutzer.uid=public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE public.tbl_person.person_id= ?";
return $this->execReadOnlyQuery($qry_betr, [$bperson_id]);
@@ -298,6 +318,128 @@ class Projektarbeit_model extends DB_Model
// paarbeit sollte nur ab einem Studiensemester online bewertet werden
return $version === null ? null : $version->isCurrent;
}
public function getProjektarbeitenForStudiengang($studiengang_kz) {
// TODO: select less fields, a lot of useless data over here
$old_qry = "SELECT *,
(SELECT orgform_kurzbz
FROM tbl_prestudentstatus
WHERE prestudent_id=(Select prestudent_id from tbl_student where student_uid=xy.uid limit 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1
) as organisationsform
FROM (SELECT DISTINCT ON(tbl_projektarbeit.projektarbeit_id) public.tbl_studiengang.bezeichnung as stgbez,tbl_projekttyp.bezeichnung AS prjbez,* FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer on(uid=student_uid)
LEFT JOIN public.tbl_person on(tbl_benutzer.person_id=tbl_person.person_id)
LEFT JOIN lehre.tbl_lehreinheit using(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung using(lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang using(studiengang_kz)
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
WHERE (projekttyp_kurzbz='Bachelor' OR projekttyp_kurzbz='Diplom')
AND public.tbl_benutzer.aktiv
AND lehre.tbl_projektarbeit.note IS NULL
AND public.tbl_studiengang.studiengang_kz = ?
ORDER BY tbl_projektarbeit.projektarbeit_id desc) as xy
ORDER BY nachname";
$new_qry = "
SELECT
DISTINCT ON(tbl_projektarbeit.projektarbeit_id)
tbl_projektarbeit.projekttyp_kurzbz,
tbl_projektarbeit.titel,
tbl_projektarbeit.projektarbeit_id,
tbl_studiengang.typ, tbl_studiengang.kurzbz,
student_benutzer.uid as student_uid,
student_person.vorname as student_vorname,
student_person.nachname as student_nachname,
tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz,
betreuer_benutzer.uid as betreuer_benutzer_uid,
betreuer_person.vorname as betreuer_vorname,
betreuer_person.nachname as betreuer_nachname,
lehre.tbl_projektbetreuer.betreuerart_kurzbz as betreuerart,
lehre.tbl_projektbetreuer.person_id as betreuer_person_id,
lehre.tbl_projektarbeit.sprache as sprache,
lehre.tbl_projektarbeit.seitenanzahl as seitenanzahl,
lehre.tbl_projektarbeit.kontrollschlagwoerter as kontrollschlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter as schlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter_en as schlagwoerter_en,
lehre.tbl_projektarbeit.abstract as abstract,
lehre.tbl_projektarbeit.abstract_en as abstract_en,
lehre.tbl_projektarbeit.insertamum as insertamum,
(
SELECT orgform_kurzbz
FROM tbl_prestudentstatus
WHERE prestudent_id = (SELECT prestudent_id
FROM tbl_student
WHERE student_uid = student_benutzer.uid
LIMIT 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
)
as organisationsform,
(
SELECT person_id
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter')
LIMIT 1
)
AS zweitbetreuer_person_id,
(
SELECT betreuerart_kurzbz
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter')
LIMIT 1
)
AS zweitbetreuer_betreuerart_kurzbz,
(
SELECT tbl_betreuerart.beschreibung
FROM lehre.tbl_projektbetreuer
JOIN lehre.tbl_betreuerart USING (betreuerart_kurzbz)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
AS zweitbetreuer_betreuerart_beschreibung,
(
SELECT trim(COALESCE(titelpre, '') || ' ' || COALESCE(vorname, '') || ' ' || COALESCE(nachname, '') || ' ' ||
COALESCE(titelpost, ''))
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer ON (lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_benutzer ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter')
LIMIT 1
)
as zweitbetreuer_full_name
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON (student_benutzer.uid = lehre.tbl_projektarbeit.student_uid)
LEFT JOIN public.tbl_person student_person ON (student_benutzer.person_id = student_person.person_id)
LEFT JOIN public.tbl_student on(student_benutzer.uid = public.tbl_student.student_uid)
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON (public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz)
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN public.tbl_person betreuer_person ON (betreuer_person.person_id = lehre.tbl_projektbetreuer.person_id)
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON (betreuer_person.person_id = betreuer_benutzer.person_id)
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv AND (lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbegutachter' OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Begutachter')
-- AND lehre.tbl_projektarbeit.note IS NULL
-- AND public.tbl_studiengang.studiengang_kz= 257
AND public.tbl_studiengang.studiengang_kz = ?
ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
";
// $oldres = $this->execReadOnlyQuery($old_qry, array($studiengang_kz));
// $newres = $this->execReadOnlyQuery($new_qry, array($studiengang_kz));
// return array($newres, $oldres);
return $this->execReadOnlyQuery($new_qry, array($studiengang_kz));
}
/**
* Holt sich Version der Projektarbeit.
+1
View File
@@ -24,6 +24,7 @@ $includesArray = array(
'vendor/npm-asset/primevue/inputnumber/inputnumber.min.js',
'vendor/npm-asset/primevue/speeddial/speeddial.min.js',
'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/moment/luxonjs/luxon.min.js'
),
'customJSModules' => array(
'public/js/apps/Abgabetool/Abgabetool.js',
+7
View File
@@ -100,4 +100,11 @@ export default {
url: '/api/frontend/v1/Abgabe/getNoten'
};
},
getProjektarbeitenForStudiengang(studiengang_kz) {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getProjektarbeitenForStudiengang',
params: { studiengang_kz }
};
}
};
+2
View File
@@ -2,6 +2,7 @@ import PluginsPhrasen from '../../plugins/Phrasen.js';
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
import {capitalize} from "../../helpers/StringHelpers.js";
const app = Vue.createApp({
name: 'AbgabetoolApp',
@@ -51,6 +52,7 @@ const app = Vue.createApp({
</template>
`
});
app.config.globalProperties.$capitalize = capitalize;
app.use(primevue.config.default, {
zIndex: {
overlay: 9000,
+11 -1
View File
@@ -14,11 +14,13 @@ import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js";
import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../../components/Cis/Mylv/RoomInformation.js";
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js";
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
import Studium from "../../components/Cis/Studium/Studium.js";
import ApiRenderers from '../../api/factory/renderers.js';
import ApiRouteInfo from '../../api/factory/routeinfo.js';
import {capitalize} from "../../helpers/StringHelpers.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -55,6 +57,12 @@ const router = VueRouter.createRouter({
component: AbgabetoolMitarbeiter,
props: true
},
{
path: `/Cis/Abgabetool/Assistenz`,
name: 'AbgabetoolAssistenz',
component: AbgabetoolAssistenz,
props: true
},
{
path: `/Cis/Abgabetool/Deadlines/:person_uid_prop?`,
name: 'DeadlineOverview',
@@ -275,6 +283,7 @@ const app = Vue.createApp({
}
},
async created(){
await this.$api
.call(ApiRenderers.loadRenderers())
.then(res => res.data)
@@ -315,6 +324,7 @@ const app = Vue.createApp({
},
mounted() {
document.addEventListener('click', this.handleClick);
},
beforeUnmount() {
document.removeEventListener('click', this.handleClick);
@@ -323,7 +333,7 @@ const app = Vue.createApp({
// kind of a bandaid for bad css on some pages to avoid horizontal scroll
setScrollbarWidth();
app.config.globalProperties.$capitalize = capitalize;
app.use(router);
app.use(primevue.config.default, {
zIndex: {
+3 -1
View File
@@ -46,7 +46,8 @@ export default {
"hiddenBsModal",
"hidePreventedBsModal",
"showBsModal",
"shownBsModal"
"shownBsModal",
"toggleFullscreen"
],
methods: {
dispose() {
@@ -66,6 +67,7 @@ export default {
},
toggleFullscreen() {
this.fullscreen = !this.fullscreen
this.$emit('toggleFullscreen')
}
},
mounted() {
@@ -27,12 +27,15 @@ export const AbgabeMitarbeiterDetail = {
projektarbeit: {
type: Object,
default: null
},
isFullscreen: {
type: Boolean,
default: false
}
},
data() {
return {
showAutomagicModalPhrase: false,
sdModel: [],
eidAkzeptiert: false,
enduploadTermin: null,
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
@@ -60,8 +63,8 @@ export const AbgabeMitarbeiterDetail = {
}
},
methods: {
openZusatzdatenModal(termin) {
getPlaceholderTermin(termin) {
return termin?.bezeichnung?.bezeichnung ?? this.$p.t('abgabetool/abgabetypPlaceholder')
},
saveTermin(termin) {
const paabgabe_id = termin.paabgabe_id
@@ -97,10 +100,10 @@ export const AbgabeMitarbeiterDetail = {
// really bad feature imo that will be annoying to deal with
// termin is completely new and has negative note
const savedNewWithNegative = !existingTerminRes && !newTerminRes.note?.positiv
const savedNewWithNegative = !existingTerminRes && !newTerminRes.note?.positiv && newTerminRes.note !== null
// termin existed previously + oldTermin had different note/positive note or no note at all
const savedExistingNoteAsNegativeAndWasNotNegativeBefore = existingTerminRes && !newTerminRes.note?.positiv && (existingTerminRes.note?.positiv || existingTerminRes.note === undefined)
const savedExistingNoteAsNegativeAndWasNotNegativeBefore = existingTerminRes && !newTerminRes.note?.positiv && newTerminRes.note !== null && (existingTerminRes.note?.positiv || existingTerminRes.note === undefined)
const openModalDueToNegativeBeurteilung = savedNewWithNegative || savedExistingNoteAsNegativeAndWasNotNegativeBefore
if(openModalDueToNegativeBeurteilung) {
@@ -198,6 +201,9 @@ export const AbgabeMitarbeiterDetail = {
getOptionLabelAbgabetyp(option){
return option.bezeichnung
},
getOptionDisabled(option) {
return !option.aktiv
},
getNotenOptionLabel(option) {
return option.bezeichnung
},
@@ -298,8 +304,23 @@ export const AbgabeMitarbeiterDetail = {
},
computed: {
activeAbgabeTypeOptions() {
return this.abgabeTypeOptions?.filter(opt => opt.aktiv === true)
getActiveIndexTabArray() {
// here we try to do mind reading logic by assuming which abgabetermine are the most relevant to the current user
// lets try to take the termin with nearest date and watch who complains and why
let closestIndex = -1;
let minDiff = Infinity;
const today = new Date();
this.projektarbeit.abgabetermine.forEach((obj, i) => {
const diff = Math.abs(new Date(obj.datum) - today);
if (diff < minDiff) {
minDiff = diff;
closestIndex = i;
}
});
return [closestIndex]
},
getEid() {
return this.$p.t('abgabetool/c4eidesstattlicheErklaerung')
@@ -323,12 +344,16 @@ export const AbgabeMitarbeiterDetail = {
})
return qgatefound
},
getPageWrapperStyle() {
return 'position: relative; min-height: 100vh;'
},
getSpeedDialStyle() {
return 'position: static !important;'
},
getSpeedDialWrapperStyle() {
// fullscreen -> position wrapper fixed in right bottom corner of viewport/screen
if(this.isFullscreen) return 'position: fixed; z-index: 9999; bottom: 24px; right: 24px;'
// non fullscreen -> wrapper is positioned on right bottom corner of modal, wherever that is
return 'position: absolute; z-index: 9999; bottom: -28px; right: -28px;'
},
getTooltipVerspaetet() {
return {
value: this.$p.t('abgabetool/c4tooltipVerspaetet'),
@@ -358,6 +383,24 @@ export const AbgabeMitarbeiterDetail = {
value: this.$p.t('abgabetool/c4tooltipAbgegeben'),
class: "custom-tooltip"
}
},
getTooltipFixtermin() {
return {
value: this.$p.t('abgabetool/c4tooltipFixtermin'),
class: "custom-tooltip"
}
},
getTooltipNotAllowedToSave() {
return {
value: this.$p.t('abgabetool/c4notAllowedToEditAbgabeTermin'),
class: "custom-tooltip"
}
},
getTooltipNotAllowedToDelete() {
return {
value: this.$p.t('abgabetool/c4notAllowedToDeleteAbgabeTermin'),
class: "custom-tooltip"
}
}
},
watch: {
@@ -378,23 +421,23 @@ export const AbgabeMitarbeiterDetail = {
},
template: `
<bs-modal
id="innerModalNewAbgabe"
ref="modalContainerCreateNewAbgabe"
class="bootstrap-prompt"
dialogClass="bordered-modal modal-lg"
:backdrop="true"
@hideBsModal="console.log('hideBsModal'); showAutomagicModalPhrase=false;"
>
<template v-slot:title>
<div>
{{ $p.t('abgabetool/c4newAbgabetermin') }}
</div>
</template>
<template v-slot:default>
<div v-if="showAutomagicModalPhrase" class="text-center"><p>{{$p.t('abgabetool/c4abgabeQualGateNegativAddNewAutomagisch')}}</p></div>
<bs-modal
id="innerModalNewAbgabe"
ref="modalContainerCreateNewAbgabe"
class="bootstrap-prompt"
dialogClass="bordered-modal modal-lg"
:backdrop="true"
@hideBsModal="console.log('hideBsModal'); showAutomagicModalPhrase=false;"
>
<template v-slot:title>
<div>
{{ $p.t('abgabetool/c4newAbgabetermin') }}
</div>
</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">
<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>-->
@@ -407,12 +450,132 @@ export const AbgabeMitarbeiterDetail = {
<!-- </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">
<VueDatePicker
v-model="newTermin.datum"
:clearable="false"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</div>
</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">
<Dropdown
:style="{'width': '100%'}"
v-model="newTermin.bezeichnung"
:options="abgabeTypeOptions"
:optionLabel="getOptionLabelAbgabetyp"
scrollHeight="300px">
</Dropdown>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{ $capitalize( $p.t('abgabetool/c4upload_allowed') )}}</div>
<div class="col-8 col-md-9">
<Checkbox
v-model="newTermin.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">{{ $capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-8 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="newTermin.kurzbz" rows="1" :cols=" isMobile ? 30 : 60"></Textarea>
</div>
</div>
</div>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" @click="handleSaveNewAbgabe(newTermin)">{{$capitalize( $p.t('abgabetool/c4saveNewAbgabetermin') )}}</button>
</template>
</bs-modal>
<div v-if="projektarbeit">
<div id="speedDialWrapper" :style="getSpeedDialWrapperStyle">
<SpeedDial
:style="getSpeedDialStyle"
:model="speedDialItems"
direction="up"
:radius="80"
type="linear"
buttonClass="p-button-rounded p-button-lg p-button-primary"
:tooltipOptions="{ position: 'left' }"
/>
</div>
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeMitarbeiterbereich') )}}</h5>
<div class="row">
<div class="col-6">
<p> {{projektarbeit?.student}}</p>
<p> {{projektarbeit?.titel}}</p>
<p v-if="projektarbeit?.zweitbegutachter"> {{projektarbeit?.zweitbegutachter}}</p>
</div>
<div v-if="!isMobile" class="col-3"></div>
<div :class="isMobile ? 'col-6' : 'col-3'">
<SpeedDial
:model="speedDialItems"
direction="left"
:radius="80"
type="linear"
buttonClass="p-button-rounded p-button-lg p-button-primary"
:tooltipOptions="{ position: 'down' }"
/>
</div>
</div>
<Accordion :multiple="true" :activeIndex="getActiveIndexTabArray">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="getDateStyleClass(termin) + '-header'">
<template #header>
<div class="d-flex row w-100">
<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>
</div>
<div class="col-auto text-start" style="min-width: max(150px, 15%); max-width: min(300px, 30%); transform: translateX(-30px)">
<span>{{ termin?.bezeichnung?.bezeichnung }}</span>
</div>
<div class="col-auto text-start" style="min-width: 100px; 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>
</div>
</template>
<!-- <div class="row">-->
<!-- <div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixterminv2')}}</div>-->
<!-- <div class="col-8 col-md-9">-->
<!-- <Checkbox -->
<!-- v-model="termin.fixtermin"-->
<!-- disabled-->
<!-- :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/c4zieldatum')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="col-8 col-md-9">
<VueDatePicker
v-model="newTermin.datum"
v-model="termin.datum"
:clearable="false"
:disabled="!termin.allowedToSave"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
@@ -421,196 +584,106 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabetypv2') )}}</div>
<div class="col-8 col-md-9">
<Dropdown
:style="{'width': '100%'}"
v-model="newTermin.bezeichnung"
:options="activeAbgabeTypeOptions"
:disabled="!termin.allowedToSave"
:placeholder="getPlaceholderTermin(termin)"
v-model="termin.bezeichnung"
@change="handleChangeAbgabetyp(termin)"
:options="abgabeTypeOptions"
:optionLabel="getOptionLabelAbgabetyp"
scrollHeight="300px">
:optionDisabled="getOptionDisabled">
</Dropdown>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4upload_allowed')}}</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">
<Checkbox
v-model="newTermin.upload_allowed"
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="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">
<Textarea style="margin-bottom: 4px;" v-model="newTermin.kurzbz" rows="1" :cols=" isMobile ? 30 : 60"></Textarea>
<Dropdown
:style="{'width': '100%'}"
v-model="termin.note"
:options="allowedNotenOptions"
:optionLabel="getNotenOptionLabel">
</Dropdown>
</div>
</div>
</div>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" @click="handleSaveNewAbgabe(newTermin)">{{ $p.t('abgabetool/c4saveNewAbgabetermin') }}</button>
</template>
</bs-modal>
<div v-if="projektarbeit">
<div style="position: fixed; bottom: 24px; right: 24px; z-index: 9999;">
<SpeedDial
:style="getSpeedDialStyle"
:model="speedDialItems"
direction="up"
:radius="80"
type="linear"
buttonClass="p-button-rounded p-button-lg p-button-primary"
:tooltipOptions="{ position: 'left' }"
/>
</div>
<h5>{{$p.t('abgabetool/c4abgabeMitarbeiterbereich')}}</h5>
<div class="row">
<div class="col-6">
<p> {{projektarbeit?.student}}</p>
<p> {{projektarbeit?.titel}}</p>
<p v-if="projektarbeit?.zweitbegutachter"> {{projektarbeit?.zweitbegutachter}}</p>
</div>
</div>
<Accordion :multiple="true" :activeIndex="[0]">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="getDateStyleClass(termin) + '-header'">
<template #header>
<div class="d-flex row w-100">
<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>
</div>
<div class="col-6 text-start">
<span>{{getAccTabHeaderForTermin(termin)}}</span>
</div>
</div>
</template>
<div class="row">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixterminv2')}}</div>
<div class="col-8 col-md-9">
<!-- always keep fixtermin checkbox disabled for mitarbeiter tool -->
<Checkbox
v-model="termin.fixtermin"
disabled
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</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">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" :cols=" isMobile ? 30 : 90" :disabled="!termin.allowedToSave"></Textarea>
</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="!termin.allowedToSave"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</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">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" :cols=" isMobile ? 30 : 90" :disabled="!termin.allowedToSave"></Textarea>
</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">
<Dropdown
:style="{'width': '100%'}"
:disabled="!termin.allowedToSave"
v-model="termin.bezeichnung"
@change="handleChangeAbgabetyp(termin)"
:options="activeAbgabeTypeOptions"
:optionLabel="getOptionLabelAbgabetyp">
</Dropdown>
</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>
<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">{{$p.t('abgabetool/c4upload_allowed')}}</div>
<div class="col-8 col-md-9">
<Checkbox
v-model="termin.upload_allowed"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
</div>
<div class="row mt-2" v-if="termin.bezeichnung?.benotbar">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4note')}}</div>
<div class="col-8 col-md-9">
<Dropdown
:style="{'width': '100%'}"
v-model="termin.note"
:options="allowedNotenOptions"
:optionLabel="getNotenOptionLabel">
</Dropdown>
</div>
</div>
<div class="row mt-2" v-if="termin.bezeichnung?.benotbar">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4notizQualGatev2')}}</div>
<div class="col-8 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" :cols=" isMobile ? 30 : 90" :disabled="!termin.allowedToSave"></Textarea>
</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="1" :cols=" isMobile ? 30 : 90" :disabled="!termin.allowedToSave"></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">
<template v-if="termin?.abgabedatum">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<button v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" class="btn btn-primary">
<a> {{$p.t('abgabetool/c4downloadAbgabe')}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
</template>
<template v-else>
{{ $p.t('abgabetool/c4nochNichtsAbgegeben') }}
</template>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">
<!-- TODO: row description? -->
</div>
<div class="col-8 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)">
{{ $p.t('abgabetool/c4save') }}
</div>
<div class="col-8 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)">
{{ $capitalize( $p.t('abgabetool/c4save') )}}
<i class="fa-solid fa-floppy-disk"></i>
</button>
<div v-else v-tooltip.right="getTooltipNotAllowedToSave">
<button disabled style="max-height: 40px;" class="btn btn-primary border-0">
{{$capitalize( $p.t('abgabetool/c4save') )}}
<i class="fa-solid fa-floppy-disk"></i>
</button>
</div>
<div class="col-auto">
<button v-if="termin.allowedToDelete && termin.paabgabe_id > 0" style="max-height: 40px;" class="btn btn-danger border-0" @click="handleDeleteTermin(termin)">
{{ $p.t('abgabetool/c4delete') }}
</div>
<div class="col-auto">
<button v-if="termin.allowedToDelete && termin.paabgabe_id > 0" style="max-height: 40px;" class="btn btn-danger border-0" @click="handleDeleteTermin(termin)">
{{$capitalize( $p.t('abgabetool/c4delete') )}}
<i class="fa-solid fa-trash"></i>
</button>
<div v-else v-tooltip.right="getTooltipNotAllowedToDelete">
<button disabled style="max-height: 40px;" class="btn btn-danger border-0">
{{$capitalize( $p.t('abgabetool/c4delete') )}}
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
</AccordionTab>
</template>
</Accordion>
</div>
</div>
</AccordionTab>
</template>
</Accordion>
</div>
`,
};
@@ -48,7 +48,7 @@ export const AbgabeStudentDetail = {
methods: {
async validate(termin, endupload = false) {
if(!termin.file.length) {
this.$fhcAlert.alertWarning(this.$p.t('global/warningChooseFile'));
this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningChooseFile')));
return false
}
@@ -57,49 +57,49 @@ export const AbgabeStudentDetail = {
// check these input fields for length of entry
if(this.form['abstract'].length < 100 && await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/warningShortAbstract'),
acceptLabel: this.$p.t('abgabetool/c4AcceptAndProceed'),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'btn btn-danger',
rejectLabel: this.$p.t('abgabetool/c4Cancel'),
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'btn btn-outline-secondary'
}) === false) {
return false
}
if(this.form['abstract_en'].length < 100 && await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/warningShortAbstractEn'),
acceptLabel: this.$p.t('abgabetool/c4AcceptAndProceed'),
message: this.$capitalize(this.$p.t('abgabetool/warningShortAbstractEn')),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'btn btn-danger',
rejectLabel: this.$p.t('abgabetool/c4Cancel'),
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'btn btn-outline-secondary'
}) === false) {
return false
}
if(this.form['schlagwoerter'].length < 50 && await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/warningShortSchlagwoerter'),
acceptLabel: this.$p.t('abgabetool/c4AcceptAndProceed'),
message: this.$capitalize(this.$p.t('abgabetool/warningShortSchlagwoerter')),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'btn btn-danger',
rejectLabel: this.$p.t('abgabetool/c4Cancel'),
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'btn btn-outline-secondary'
}) === false) {
return false
}
if(this.form['schlagwoerter_en'].length < 50 && await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/warningShortSchlagwoerterEn'),
acceptLabel: this.$p.t('abgabetool/c4AcceptAndProceed'),
message: this.$capitalize(this.$p.t('abgabetool/warningShortSchlagwoerterEn')),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'btn btn-danger',
rejectLabel: this.$p.t('abgabetool/c4Cancel'),
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'btn btn-outline-secondary'
}) === false) {
return false
}
if(this.form['seitenanzahl'] <= 5 && await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/warningSmallSeitenanzahl'),
acceptLabel: this.$p.t('abgabetool/c4AcceptAndProceed'),
message: this.$capitalize(this.$p.t('abgabetool/warningSmallSeitenanzahl')),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'btn btn-danger',
rejectLabel: this.$p.t('abgabetool/c4Cancel'),
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'btn btn-outline-secondary'
}) === false) {
return false
@@ -192,13 +192,13 @@ export const AbgabeStudentDetail = {
},
handleUploadRes(res, termin) {
if(res.meta.status == "success") {
this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/c4fileUploadSuccessv3'))
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4fileUploadSuccessv3')))
// update 'abgabedatum' for successful upload -> shows the pdf icon and date once set
termin.abgabedatum = new Date().toISOString().split('T')[0];
} else {
this.$fhcAlert.alertError(this.$p.t('abgabetool/c4fileUploadErrorv3'))
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4fileUploadErrorv3')))
}
if(res.meta.signaturInfo) {
@@ -262,8 +262,26 @@ export const AbgabeStudentDetail = {
}
},
computed: {
getActiveIndexTabArray() {
// here we try to do mind reading logic by assuming which abgabetermine are the most relevant to the current user
// lets try to take the termin with nearest date and watch who complains and why
let closestIndex = -1;
let minDiff = Infinity;
const today = new Date();
this.projektarbeit.abgabetermine.forEach((obj, i) => {
const diff = Math.abs(new Date(obj.datum) - today);
if (diff < minDiff) {
minDiff = diff;
closestIndex = i;
}
});
return [closestIndex]
},
getEid() {
return this.$p.t('abgabetool/c4eidesstattlicheErklaerung')
return this.$capitalize(this.$p.t('abgabetool/c4eidesstattlicheErklaerung'))
},
getAllowedToSendEndupload() {
return !this.eidAkzeptiert
@@ -280,37 +298,56 @@ export const AbgabeStudentDetail = {
},
getTooltipVerspaetet() {
return {
value: this.$p.t('abgabetool/c4tooltipVerspaetet'),
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerspaetet')),
class: "custom-tooltip"
}
},
getTooltipVerpasst() {
return {
value: this.$p.t('abgabetool/c4tooltipVerpasst'),
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerpasst')),
class: "custom-tooltip"
}
},
getTooltipAbzugeben() {
return {
value: this.$p.t('abgabetool/c4tooltipAbzugeben'),
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbzugeben')),
class: "custom-tooltip"
}
},
getTooltipStandard() {
return {
value: this.$p.t('abgabetool/c4tooltipStandard'),
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipStandard')),
class: "custom-tooltip"
}
},
getTooltipAbgegeben() {
return {
value: this.$p.t('abgabetool/c4tooltipAbgegeben'),
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgegeben')),
class: "custom-tooltip"
}
},
getTooltipFixtermin() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipFixtermin')),
class: "custom-tooltip"
}
},
getTooltipNotAllowedToUpload() {
if(this.isViewMode) {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedInViewMode')),
class: "custom-tooltip"
}
} else {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedRegular')),
class: "custom-tooltip"
}
}
}
},
created() {
},
mounted() {
@@ -320,16 +357,15 @@ export const AbgabeStudentDetail = {
<i class="fa-solid fa-spinner fa-pulse fa-5x"></i>
</div>
<div v-if="projektarbeit">
<h5>{{$p.t('abgabetool/c4abgabeStudentenbereich')}}</h5>
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereich') )}}</h5>
<div class="row">
<p> {{projektarbeit?.betreuer}}</p>
<p> {{projektarbeit?.titel}}</p>
</div>
<Accordion :multiple="true" :activeIndex="[0]">
<Accordion :multiple="true" :activeIndex="getActiveIndexTabArray">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="getDateStyleClass(termin) + '-header'">
<template #header>
@@ -341,26 +377,32 @@ export const AbgabeStudentDetail = {
<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>
</div>
<div class="col-6 text-start">
<span>{{getAccTabHeaderForTermin(termin)}}</span>
<div class="col-auto text-start" style="min-width: max(150px, 15%); 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)">
<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>
</div>
</template>
<div class="row">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixterminv2')}}</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 class="row">-->
<!-- <div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4fixterminv2')}}</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 class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="col-8 col-md-9">
<VueDatePicker
v-model="termin.datum"
@@ -375,57 +417,56 @@ export const AbgabeStudentDetail = {
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabetypv2') )}}</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="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 v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-start align-items-start">
<div 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/c4notizQualGatev2')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-8 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" :cols=" isMobile ? 30 : 90" 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">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-8 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" :cols=" isMobile ? 25 : 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-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> {{$p.t('abgabetool/c4downloadAbgabe')}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
<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>
{{ $p.t('abgabetool/c4nochNichtsAbgegeben') }}
{{ $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">{{$p.t('abgabetool/c4fileupload')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4fileupload') )}}</div>
<div class="col-8 col-md-9">
<div class="row">
<div class="row" v-if="termin?.allowedToUpload">
<div class="col-12 col-sm-6 mb-2">
<Upload
:disabled="!termin?.allowedToUpload || isViewMode"
accept=".pdf"
v-model="termin.file"
></Upload>
@@ -434,9 +475,27 @@ export const AbgabeStudentDetail = {
<button
class="btn btn-primary border-0 w-100"
@click="upload(termin)"
:disabled="!termin.allowedToUpload || isViewMode"
>
{{$p.t('abgabetool/c4upload')}}
{{$capitalize( $p.t('abgabetool/c4upload') )}}
<i class="fa-solid fa-upload"></i>
</button>
</div>
</div>
<div class="row" v-else-if="!termin?.allowedToUpload || isViewMode" v-tooltip.right="getTooltipNotAllowedToUpload">
<div class="col-12 col-sm-6 mb-2">
<Upload
disabled
accept=".pdf"
v-model="termin.file"
></Upload>
</div>
<div class="col-12 col-sm-6">
<button
class="btn btn-primary border-0 w-100"
@click="upload(termin)"
disabled
>
{{$capitalize( $p.t('abgabetool/c4upload') )}}
<i class="fa-solid fa-upload"></i>
</button>
</div>
@@ -454,7 +513,7 @@ export const AbgabeStudentDetail = {
dialogClass="bordered-modal modal-lg">
<template v-slot:title>
<div>
{{$p.t('abgabetool/c4enduploadZusatzdaten')}}
{{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}}
</div>
<div class="row mb-3 align-items-start">
@@ -463,13 +522,13 @@ export const AbgabeStudentDetail = {
</div>
<div class="row mb-3 align-items-start">
<p class="ml-4 mr-4">{{$p.t('abgabetool/c4titel')}}: {{ projektarbeit?.titel }}</p>
<p class="ml-4 mr-4">{{$capitalize( $p.t('abgabetool/c4titel') )}}: {{ projektarbeit?.titel }}</p>
</div>
</template>
<template v-slot:default>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4Sprache')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4Sprache') )}}</div>
<div class="row">
<Dropdown
:style="{'width': '100%'}"
@@ -490,21 +549,21 @@ export const AbgabeStudentDetail = {
<!-- -->
<!-- </div>-->
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4schlagwoerterGer')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}</div>
<div class="row">
<Textarea v-model="form.schlagwoerter"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4schlagwoerterEng')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4schlagwoerterEng') )}}</div>
<div class="row">
<Textarea v-model="form.schlagwoerter_en"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4abstractGer')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4abstractGer') )}}</div>
<div class="row">
<Textarea v-model="form.abstract" rows="10" maxlength="5000"></Textarea>
<p>{{ form.abstract?.length ? form.abstract.length : 0 }} / 5000 characters</p>
@@ -512,7 +571,7 @@ export const AbgabeStudentDetail = {
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4abstractEng')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4abstractEng') )}}</div>
<div class="row">
<Textarea v-model="form.abstract_en" rows="10" maxlength="5000"></Textarea>
<p>{{ form.abstract_en?.length ? form.abstract_en.length : 0 }} / 5000 characters</p>
@@ -520,7 +579,7 @@ export const AbgabeStudentDetail = {
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4seitenanzahl')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4seitenanzahl') )}}</div>
<div class="row">
<InputNumber
v-model="form.seitenanzahl"
@@ -533,7 +592,7 @@ export const AbgabeStudentDetail = {
<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-2"><p>{{$capitalize( $p.t('abgabetool/c4gelesenUndAkzeptiert') )}}</p></div>
<div class="col-1">
<Checkbox
@@ -548,7 +607,7 @@ export const AbgabeStudentDetail = {
</template>
<template v-slot:footer>
<button class="btn btn-primary" :disabled="getAllowedToSendEndupload" @click="triggerEndupload">{{$p.t('ui/hochladen')}}</button>
<button class="btn btn-primary" :disabled="getAllowedToSendEndupload" @click="triggerEndupload">{{$capitalize( $p.t('ui/hochladen') )}}</button>
</template>
</bs-modal>
@@ -0,0 +1,534 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import AbgabeDetail from "./AbgabeMitarbeiterDetail.js";
import VerticalSplit from "../../verticalsplit/verticalsplit.js"
import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
export const AbgabetoolAssistenz = {
name: "AbgabetoolAssistenz",
components: {
BsModal,
CoreFilterCmpt,
AbgabeDetail,
VerticalSplit,
Dropdown: primevue.dropdown,
Textarea: primevue.textarea,
VueDatePicker
},
provide() {
return {
abgabeTypeOptions: Vue.computed(() => this.abgabeTypeOptions),
allowedNotenOptions: Vue.computed(() => this.allowedNotenOptions),
turnitin_link: Vue.computed(() => this.turnitin_link),
old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link)
}
},
props: {
viewData: {
type: Object,
required: true,
default: () => ({name: '', uid: ''}),
validator(value) {
return value && value.name && value.uid
}
}
},
data() {
return {
detailIsFullscreen: false,
showZweitbetreuerCol: false,
phrasenPromise: null,
phrasenResolved: false,
turnitin_link: null,
old_abgabe_beurteilung_link: null,
saving: false,
loading: false,
abgabeTypeOptions: null,
notenOptions: null,
allowedNotenOptions: null,
serienTermin: Vue.reactive({
datum: new Date(),
bezeichnung: {
paabgabetyp_kurzbz: 'zwischen',
bezeichnung: 'Zwischenabgabe'
},
kurzbz: ''
}),
showAll: false,
tabulatorUuid: Vue.ref(0),
selectedData: [],
domain: '',
student_uid: null,
detail: null,
detailOffset: 0,
projektarbeiten: null,
selectedProjektarbeit: null,
tableBuiltResolve: null,
tableBuiltPromise: null,
abgabeTableOptions: {
minHeight: 250,
index: 'projektarbeit_id',
layout: 'fitDataStretch',
placeholder: this.$p.t('global/noDataAvailable'),
selectable: true,
selectableCheck: this.selectionCheck,
rowHeight: 80,
columns: [
{
formatter: 'rowSelection',
titleFormatter: 'rowSelection',
titleFormatterParams: {
rowRange: "active" // Only toggle the values of the active filtered rows
},
hozAlign:"center",
headerSort: false,
frozen: true,
width: 70
},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
// {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))), headerFilter: true, field: 'prevTermin', formatter: this.abgabterminFormatter, widthGrow: 1, tooltip: false}
// {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), headerFilter: true, field: 'nextTermin', formatter: this.abgabterminFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4kontakt'))), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuer'))), field: 'erstbetreuer', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuer'))), field: 'zweitbetreuer', formatter: this.centeredTextFormatter, widthGrow: 1, visible: this.showZweitbetreuerCol}
],
persistence: false,
},
abgabeTableEventHandlers: [{
event: "tableBuilt",
handler: async () => {
this.tableBuiltResolve()
}
},
{
event: "cellClick",
handler: async (e, cell) => {
if(cell.getColumn().getField() === "details") {
this.setDetailComponent(cell.getValue())
this.undoSelection(cell)
} else if (cell.getColumn().getField() === "mail") {
this.undoSelection(cell)
}
}
},
{
event: "rowSelectionChanged",
handler: async(data) => {
this.selectedData = data
}
}
]};
},
methods: {
handleToggleFullscreenDetail() {
this.detailIsFullscreen = !this.detailIsFullscreen
},
getOptionLabelAbgabetyp(option){
return option.bezeichnung
},
formatDate(dateParam) {
const date = new Date(dateParam)
// handle missing leading 0
const padZero = (num) => String(num).padStart(2, '0');
const month = padZero(date.getMonth() + 1); // Months are zero-based
const day = padZero(date.getDate());
const year = date.getFullYear();
return `${day}.${month}.${year}`;
},
undoSelection(cell) {
// checks if cells row is selected and unselects -> imitates columns which dont trigger row selection
// but actually just revert it after the fact
const row = cell.getRow()
if(row.isSelected()) {
row.deselect();
}
},
selectionCheck(row) {
const data = row.getData()
if(data?.betreuerart_kurzbz == 'Zweitbegutachter') return false
return true
},
showDeadlines(){
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router
+ '/Cis/Abgabetool/Deadlines'
window.open(link, '_blank')
},
toggleShowAll(showall) {
this.showAll = showall
this.loading = true
this.loadProjektarbeiten(showall, () => {
this.$refs.abgabeTable?.tabulator.redraw(true)
this.$refs.abgabeTable?.tabulator.setSort([]);
this.loading = false
})
},
openAddSeriesModal() {
this.$refs.modalContainerAddSeries.show()
},
addSeries() {
this.saving = true
this.$api.call(ApiAbgabe.postSerientermin(
this.serienTermin.datum.toISOString(),
this.serienTermin.bezeichnung.paabgabetyp_kurzbz,
this.serienTermin.bezeichnung.bezeichnung,
this.serienTermin.kurzbz,
this.selectedData?.map(projekt => projekt.projektarbeit_id)
)).then(res => {
if (res.meta.status === "success" && res.data) {
this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/serienTerminGespeichert'))
// TODO: sticky lifetime erhöhen um sinnvoll lesen zu können?
this.$fhcAlert.alertInfo(this.$p.t('abgabetool/serienTerminEmailSentInfo', [this.createInfoString(res.data)]));
} else {
this.$fhcAlert.alertError(this.$p.t('abgabetool/errorSerienterminSpeichern'))
}
}).finally(()=>{
this.saving = false
})
this.$refs.modalContainerAddSeries.hide()
},
createInfoString(data) {
let str = '';
data.forEach(name => {
str += name
str += '; '
})
return str
},
isPastDate(date) {
return new Date(date) < new Date(Date.now())
},
setDetailComponent(details){
const pa = this.projektarbeiten.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
// pa.isCurrent = res.data[1]
pa.abgabetermine.forEach(termin => {
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
termin.file = []
termin.allowedToSave = termin.insertvon == this.viewData?.uid && pa.betreuerart_kurzbz != 'Zweitbegutachter'
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()
},
centeredTextFormatter(cell) {
const val = cell.getValue()
if(!val) return
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<p style="max-width: 100%; width: 100%; overflow-wrap: break-word; word-break: break-word; white-space: normal; margin: 0px; text-align: center">'+val+'</p></div>'
},
detailFormatter(cell) {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<a><i class="fa fa-folder-open" style="color:#00649C"></i></a></div>'
},
mailFormatter(cell) {
const val = cell.getValue()
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 '-'
},
pkzTextFormatter(cell) {
const val = cell.getValue()
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<p style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+val+'</p></div>'
},
abgabterminFormatter(termin) {
},
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
buildMailToLink(abgabe) {
return 'mailto:' + abgabe.student_uid +'@'+ this.domain
},
buildPKZ(projekt) {
return `${projekt.student_uid} / ${projekt.matrikelnr}`
},
buildStg(projekt) {
return (projekt.typ + projekt.kurzbz)?.toUpperCase()
},
buildErstbetreuer(projekt) {
return projekt.betreuer_vorname + ' ' + projekt.betreuer_nachname + ' - ' + projekt.betreuerart + '(' + projekt.betreuer_benutzer_uid + ')'
},
buildZweitbetreuer(projekt) {
return projekt.zweitbetreuer_full_name ?? ''
},
setupData(data){
this.projektarbeiten = data[0]
this.domain = data[1]
const d = data[0].map(projekt => {
let mode = 'detailTermine'
// only show 2tbetreuer col if any projektarbeit has one
if(projekt.zweitbetreuer_full_name) this.showZweitbetreuerCol = true
// first Abgabetermin in the past
projekt.abgabetermine.forEach(termin => {
const date = luxon.DateTime.fromISO(termin.datum)
termin.diff = date.diffNow('milliseconds')
// console.log(termin.diff)
})
// console.log('\n\n')
return {
...projekt,
abgabetermine: projekt.abgabetermine,
details: {
student_uid: projekt.uid,
projektarbeit_id: projekt.projektarbeit_id,
},
pkz: this.buildPKZ(projekt),
beurteilung: projekt.beurteilungLink ?? null,
sem: projekt.studiensemester_kurzbz,
stg: this.buildStg(projekt),
mail: this.buildMailToLink(projekt),
erstbetreuer: this.buildErstbetreuer(projekt),
zweitbetreuer: this.buildZweitbetreuer(projekt),
typ: projekt.projekttyp_kurzbz,
titel: projekt.titel
}
})
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.$refs.abgabeTable.tabulator.setData(d);
},
loadProjektarbeiten(all = false, callback) {
this.loading = true
this.$api.call(ApiAbgabe.getProjektarbeitenForStudiengang(this.getCurrentStudiengang))
.then(res => {
if(res?.data) this.setupData(res.data)
}).finally(() => {
if(callback) {
callback()
}
}).finally(()=>{
this.loading=false
})
},
loadAbgaben(details) {
return new Promise((resolve) => {
this.$api.call(ApiAbgabe.getStudentProjektabgaben(details))
.then(res => {
resolve(res)
})
})
},
handleUuidDefined(uuid) {
this.tabulatorUuid = uuid
},
calcMaxTableHeight() {
const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
const tableDataSet = document.getElementById('filterTableDataset' + tableID);
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.abgabeTableOptions.height = window.visualViewport.height - rect.top - 80
this.$refs.abgabeTable.tabulator.setHeight(this.abgabeTableOptions.height)
},
async setupMounted() {
this.tableBuiltPromise = new Promise(this.tableResolve)
await this.tableBuiltPromise
this.loadProjektarbeiten()
// this.$refs.verticalsplit.collapseBottom()
this.calcMaxTableHeight()
},
sendEmailBegutachter() {
// TODO: implement
},
sendEmailStudierende() {
// TODO: implement
}
},
watch: {
},
computed: {
getCurrentStudiengang() {
// TODO: sophisticated logic pulling from default value by viewData or dropdown select
return 257
}
},
created() {
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
// fetch config to avoid hard coded links
this.$api.call(ApiAbgabe.getConfig()).then(res => {
this.turnitin_link = res.data?.turnitin_link
this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link
}).catch(e => {
console.log(e)
this.loading = false
})
// fetch noten options
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
this.$api.call(ApiAbgabe.getNoten()).then(res => {
this.notenOptions = res.data
// TODO: more sophisticated way to filter for these two, in essence it is still hardcoded
this.allowedNotenOptions = this.notenOptions.filter(
opt => opt.bezeichnung === 'Bestanden'
|| opt.bezeichnung === 'Nicht bestanden'
)
}).catch(e => {
this.loading = false
})
// fetch abgabetypen options
this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => {
this.abgabeTypeOptions = res.data
}).catch(e => {
this.loading = false
})
},
mounted() {
this.setupMounted()
},
template: `
<template v-if="phrasenResolved">
<div id="loadingOverlay" v-show="loading || saving" style="position: absolute; width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.5); z-index: 99999999999;">
<i class="fa-solid fa-spinner fa-pulse fa-5x"></i>
</div>
<bs-modal ref="modalContainerAddSeries" class="bootstrap-prompt"
dialogClass="modal-lg">
<template v-slot:title>
<div>
{{ $p.t('abgabetool/neueTerminserie') }}
</div>
</template>
<template v-slot:default>
<div class="row">
<div class="col-3 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4zieldatum')}}
</div>
<div class="col-3 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4abgabetypv2')}}
</div>
<div class="col-6 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4abgabekurzbz')}}
</div>
</div>
<div class="row">
<div class="col-3 d-flex justify-content-center align-items-center">
<div>
<VueDatePicker
style="width: 95%;"
v-model="serienTermin.datum"
:clearable="false"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</div>
</div>
<div class="col-3 d-flex justify-content-center align-items-center">
<Dropdown
:style="{'width': '100%'}"
v-model="serienTermin.bezeichnung"
:options="abgabeTypeOptions"
:optionLabel="getOptionLabelAbgabetyp">
</Dropdown>
</div>
<div class="col-6 d-flex justify-content-center align-items-center">
<Textarea style="margin-bottom: 4px;" v-model="serienTermin.kurzbz" rows="1" cols="40"></Textarea>
</div>
</div>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" @click="addSeries">{{ $p.t('global/speichern') }}</button>
</template>
</bs-modal>
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
dialogClass="modal-xl" :allowFullscreenExpand="true"
@toggle-fullscreen="handleToggleFullscreenDetail">
<template v-slot:title>
<div>
{{$p.t('abgabetool/c4abgabeMitarbeiterDetailTitle')}}
</div>
</template>
<template v-slot:default>
<AbgabeDetail :projektarbeit="selectedProjektarbeit" :isFullscreen="detailIsFullscreen"></AbgabeDetail>
</template>
</bs-modal>
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div style="max-height:40vw;">
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
:title="''"
@uuidDefined="handleUuidDefined"
ref="abgabeTable"
:newBtnShow="true"
:newBtnLabel="$p.t('abgabetool/neueTerminserie')"
:newBtnDisabled="!selectedData.length"
@click:new=openAddSeriesModal
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
:useSelectionSpan="false"
>
<template #actions>
<button @click="sendEmailStudierende" role="button" class="btn btn-secondary ml-2">
{{ $p.t('abgabetool/sendEmailStudierende') }}
</button>
<button @click="sendEmailBegutachter" role="button" class="btn btn-secondary ml-2">
{{ $p.t('abgabetool/sendEmailBegutachter') }}
</button>
</template>
</core-filter-cmpt>
</div>
</template>
`,
};
export default AbgabetoolAssistenz;
@@ -36,6 +36,9 @@ export const AbgabetoolMitarbeiter = {
},
data() {
return {
detailIsFullscreen: false,
phrasenPromise: null,
phrasenResolved: false,
turnitin_link: null,
old_abgabe_beurteilung_link: null,
saving: false,
@@ -82,16 +85,16 @@ export const AbgabetoolMitarbeiter = {
frozen: true,
width: 70
},
{title: Vue.computed(() => this.$p.t('abgabetool/c4details')), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4personenkennzeichen')), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4kontakt')), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4vorname')), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4nachname')), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4projekttyp')), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4sem')), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4titel')), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8},
{title: Vue.computed(() => this.$p.t('abgabetool/c4betreuerart')), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1}
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4kontakt'))), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerart'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1}
],
persistence: false,
},
@@ -121,6 +124,9 @@ export const AbgabetoolMitarbeiter = {
]};
},
methods: {
handleToggleFullscreenDetail() {
this.detailIsFullscreen = !this.detailIsFullscreen
},
getOptionLabelAbgabetyp(option){
return option.bezeichnung
},
@@ -202,6 +208,7 @@ export const AbgabetoolMitarbeiter = {
return new Date(date) < new Date(Date.now())
},
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
@@ -226,6 +233,7 @@ export const AbgabetoolMitarbeiter = {
pa.abgabetermine.forEach(termin => {
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
termin.file = []
termin.allowedToSave = termin.insertvon == this.viewData?.uid && pa.betreuerart_kurzbz != 'Zweitbegutachter'
termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum
@@ -239,7 +247,7 @@ export const AbgabetoolMitarbeiter = {
this.$refs.modalContainerAbgabeDetail.show()
})
}).finally(()=>{this.loading = false})
},
centeredTextFormatter(cell) {
const val = cell.getValue()
@@ -356,6 +364,8 @@ export const AbgabetoolMitarbeiter = {
},
created() {
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
// fetch config to avoid hard coded links
this.$api.call(ApiAbgabe.getConfig()).then(res => {
this.turnitin_link = res.data?.turnitin_link
@@ -389,6 +399,7 @@ export const AbgabetoolMitarbeiter = {
this.setupMounted()
},
template: `
<template v-if="phrasenResolved">
<div id="loadingOverlay" v-show="loading || saving" style="position: absolute; width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.5); z-index: 99999999999;">
<i class="fa-solid fa-spinner fa-pulse fa-5x"></i>
</div>
@@ -406,7 +417,7 @@ export const AbgabetoolMitarbeiter = {
{{$p.t('abgabetool/c4zieldatum')}}
</div>
<div class="col-3 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4abgabetyp')}}
{{$p.t('abgabetool/c4abgabetypv2')}}
</div>
<div class="col-6 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4abgabekurzbz')}}
@@ -446,20 +457,21 @@ export const AbgabetoolMitarbeiter = {
</bs-modal>
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
dialogClass="modal-xl" :allowFullscreenExpand="true">
dialogClass="modal-xl" :allowFullscreenExpand="true"
@toggle-fullscreen="handleToggleFullscreenDetail">
<template v-slot:title>
<div>
{{$p.t('abgabetool/c4abgabeMitarbeiterDetailTitle')}}
</div>
</template>
<template v-slot:default>
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
<AbgabeDetail :projektarbeit="selectedProjektarbeit" :isFullscreen="detailIsFullscreen"></AbgabeDetail>
</template>
</bs-modal>
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div style="max-height:40vw;">
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div style="max-height:40vw;">
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
@@ -493,6 +505,7 @@ export const AbgabetoolMitarbeiter = {
</core-filter-cmpt>
</div>
</template>
`,
};
@@ -34,6 +34,9 @@ export const AbgabetoolStudent = {
},
data() {
return {
phrasenPromise: null,
phrasenResolved: false,
loading: false,
notenOptions: null,
domain: '',
student_uid: null,
@@ -64,6 +67,7 @@ export const AbgabetoolStudent = {
return new Date(date) < new Date(Date.now())
},
setDetailComponent(details){
this.loading = true
this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data[0].retval
@@ -71,16 +75,15 @@ export const AbgabetoolStudent = {
termin.file = []
termin.allowedToUpload = false
// termin.datum = '2025-10-16'
// TODO: fixtermin logic?
// TODO: change this and corresponding tooltips every time FH decides QG work different now
if(termin.paabgabetyp_kurzbz == 'end') {
termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGates(pa.abgabetermine)
} else if(termin.paabgabetyp_kurzbz == 'qualgate1' || termin.paabgabetyp_kurzbz == 'qualgate2') {
termin.allowedToUpload = termin.upload_allowed
} else if(termin.fixtermin) {
termin.allowedToUpload = !this.isPastDate(termin.datum)
} else {
termin.allowedToUpload = true
// TODO: this will confuse people so much, by requirement we should NOT show people this flag
// but it still should have an effect?
termin.allowedToUpload = termin.upload_allowed
}
})
@@ -92,7 +95,7 @@ export const AbgabetoolStudent = {
this.$refs.modalContainerAbgabeDetail.show()
// this.$refs.verticalsplit.showBoth()
})
}).finally(()=>{this.loading=false})
},
centeredTextFormatter(cell) {
const val = cell.getValue()
@@ -137,9 +140,9 @@ export const AbgabetoolStudent = {
// this.projektarbeiten = data[0]
this.domain = data[1]
this.student_uid = data[2]
this.projektarbeiten = data[0] ?? null
if(!this.projektarbeiten) return
this.projektarbeiten = this.projektarbeiten.map(projekt => {
const projektarbeiten = data[0] ?? null
if(!projektarbeiten) return
this.projektarbeiten = projektarbeiten.map(projekt => {
let mode = 'detailTermine'
if (projekt.babgeschickt || projekt.zweitbetreuer_abgeschickt) {
@@ -166,7 +169,7 @@ export const AbgabetoolStudent = {
titel: projekt.titel
}
})
},
loadProjektarbeiten() {
this.$api.call(ApiAbgabe.getStudentProjektarbeiten(this.student_uid_prop || this.viewData?.uid || null))
@@ -218,11 +221,14 @@ export const AbgabetoolStudent = {
}
},
async created() {
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
this.loading = true
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
await this.$api.call(ApiAbgabe.getNoten()).then(res => {
this.notenOptions = res.data
}).catch(e => {
}).finally(() => {
this.loading = false
})
@@ -231,12 +237,16 @@ export const AbgabetoolStudent = {
this.setupMounted()
},
template: `
<template v-if="phrasenResolved">
<div id="loadingOverlay" v-show="loading || saving" style="position: absolute; width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.5); z-index: 99999999999;">
<i class="fa-solid fa-spinner fa-pulse fa-5x"></i>
</div>
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
dialogClass="modal-xl" :allowFullscreenExpand="true">
<template v-slot:title>
<div>
{{$p.t('abgabetool/c4abgabeStudentDetailTitle')}}
{{$capitalize( $p.t('abgabetool/c4abgabeStudentDetailTitle') )}}
</div>
</template>
<template v-slot:default>
@@ -245,11 +255,11 @@ export const AbgabetoolStudent = {
</template>
</bs-modal>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<h2>{{$capitalize( $p.t('abgabetool/abgabetoolTitle') )}}</h2>
<hr>
<div v-if="projektarbeiten === null">
{{$p.t('abgabetool/c4abgabeStudentNoProjectsFound')}}
{{$capitalize( $p.t('abgabetool/c4abgabeStudentNoProjectsFound') )}}
</div>
<Accordion :multiple="true" :activeIndex="[0]">
@@ -268,30 +278,30 @@ export const AbgabetoolStudent = {
</template>
<div class="row">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4details')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4details') )}}</div>
<div class="col-8 col-md-9">
<button @click="setDetailComponent(projektarbeit.details)" class="btn btn-primary">
{{$p.t('abgabetool/c4projektdetailsOeffnen')}} <a><i class="fa fa-folder-open"></i></a>
{{$capitalize( $p.t('abgabetool/c4projektdetailsOeffnen') )}} <a><i class="fa fa-folder-open"></i></a>
</button>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4beurteilung')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4beurteilung') )}}</div>
<div class="col-8 col-md-9">
<button v-if="projektarbeit.beurteilung" @click="handleDownloadBeurteilung(projektarbeit)" class="btn btn-primary">
<a> {{$p.t('abgabetool/c4downloadBeurteilung')}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
<a> {{$capitalize( $p.t('abgabetool/c4downloadBeurteilung') )}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
<a v-else>{{$p.t('abgabetool/c4nobeurteilungVorhanden')}}</a>
<a v-else>{{$capitalize( $p.t('abgabetool/c4nobeurteilungVorhanden') )}}</a>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4sem')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4sem') )}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.sem }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4stg')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4stg') )}}</div>
<div class="col-8 col-md-9">
<div class="col-1 d-flex justify-content-start align-items-start">
{{ projektarbeit.stg }}
@@ -299,31 +309,31 @@ export const AbgabetoolStudent = {
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4betreuer')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4betreuer') )}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.betreuer }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4betreuerEmailKontakt')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4betreuerEmailKontakt') )}}</div>
<div class="col-8 col-md-9">
<a :href="getMailLink(projektarbeit)"><i class="fa fa-envelope" style="color:#00649C"></i></a>
</div>
</div>
<div v-if="projektarbeit.zweitbetreuer" class="row mt-2">
<div v-if="projektarbeit.zweitbetreuer_person_id || projektarbeit.zweitbetreuer" class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{ projektarbeit.zweitbetreuer_betreuerart_kurzbz}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.zweitbetreuer_betreuerart_beschreibung}}: {{ projektarbeit.zweitbetreuer?.first }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4projekttyp')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4projekttyp') )}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.projekttypbezeichnung }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$p.t('abgabetool/c4titel')}}</div>
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4titel') )}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.titel }}
</div>
@@ -331,7 +341,7 @@ export const AbgabetoolStudent = {
</AccordionTab>
</template>
</Accordion>
</template>
`,
};
@@ -34,7 +34,7 @@ export const DeadlineOverview = {
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/c4fixterminv2')), field: 'fixterminstring', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4abgabetyp')), field: 'typ_bezeichnung', formatter: this.centeredTextFormatter, widthGrow: 1},
{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},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', formatter: this.centeredTextFormatter,widthGrow: 1},
+4
View File
@@ -0,0 +1,4 @@
export function capitalize(string) {
if (!string) return '';
return string[0].toUpperCase() + string.slice(1);
}
+119 -3
View File
@@ -42059,18 +42059,18 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4abgabetyp',
'phrase' => 'c4abgabetypv2',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Abgabetyp",
'text' => "Projektfrist",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Type of document submitted",
'text' => "Type of project deadline",
'description' => '',
'insertvon' => 'system'
)
@@ -42916,6 +42916,78 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4tooltipFixtermin',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Für den gesamten Studiengang verbindlicher Termin.
Liegt ein Termin in der Vergangenheit, kann nichts mehr hochgeladen werden. Ist es dennoch erforderlich,
haben Studierende bei der Studiengangsassistenz um eine Korrektur dieses Termins anzusuchen.",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'This deadline is binding for the entire program. If a deadline is in the past,
nothing can be uploaded. If it is still necessary,
students must request a correction of this deadline from the program assistant.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4notAllowedToEditAbgabeTermin',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Sie sind nicht berechtigt diesen Abgabetermin zu bearbeiten.
Es muss entweder eine Benotung vorgesehen sein oder sich um einen individuellen Termin handeln,
welcher von Ihrem Benutzerkonto angelegt wurde.",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'You are not authorized to edit this deadline.
It must either be scheduled for grading or be a custom due date
created by your user account.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4notAllowedToDeleteAbgabeTermin',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Sie sind nicht berechtigt diesen Abgabetermin zu löschen.
Sie benötigen mindestens die Berechtigung den Termin zu bearbeiten und es darf noch keine
Dateiabgabe vorhanden sein.",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'You are not authorized to delete this deadline.
You need at least the permission to edit the deadline, and no file submissions must yet exist.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
@@ -42936,6 +43008,50 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4studentAbgabeNotAllowedInViewMode',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Projektabgabe im Ansichtsmodus ist nicht erlaubt!",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Project submission in view mode is not allowed!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4studentAbgabeNotAllowedRegular',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Verspätete Projektabgabe ist bei Terminen, welche von der Studiengangsassistenz für den gesamten Studiengang fixiert wurden nicht erlaubt!
Um einen Endupload durchführen zu können, müssen Sie ein positiv benotetes Quality Gate 1 & Quality Gate 2 in der relevanten Projektarbeit absolviert haben.",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Late project submissions are not permitted for deadlines set by the program assistant for the entire program!
To be able to complete a final upload, you must have successfully completed Quality Gate 1 and Quality Gate 2 for the relevant project work.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
+3 -3
View File
@@ -80,9 +80,9 @@ $trenner->loadVariables($getuid);
$sql_query = "SELECT *,
(SELECT orgform_kurzbz
FROM tbl_prestudentstatus
WHERE prestudent_id=(Select prestudent_id from tbl_student where student_uid=xy.uid limit 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1
FROM tbl_prestudentstatus
WHERE prestudent_id=(Select prestudent_id from tbl_student where student_uid=xy.uid limit 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1
) as organisationsform
FROM (SELECT DISTINCT ON(tbl_projektarbeit.projektarbeit_id) public.tbl_studiengang.bezeichnung as stgbez,tbl_projekttyp.bezeichnung AS prjbez,* FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer on(uid=student_uid)