mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-01 12:19:28 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ec876fc27 | |||
| cac3df195e | |||
| ae71517ceb | |||
| 9f462fe3d6 | |||
| 004bcc43c3 | |||
| 6d79288f33 | |||
| 2eb2c36d5b | |||
| 22eed92c86 | |||
| fa91e204f0 | |||
| 23506430b1 | |||
| fa58635a22 | |||
| 97e7e5b641 | |||
| 178c854941 | |||
| 7f630f24d5 | |||
| 557e43e19c | |||
| 790568e1e0 | |||
| dfc3096587 | |||
| c0192e9e00 | |||
| 9b823dbaa1 | |||
| 398e3aa139 | |||
| 553a33aa42 | |||
| 05e9d948ea | |||
| a5d343268f | |||
| 9e061de9bd | |||
| d0adf2dfc3 | |||
| f51d0f72c6 | |||
| c62c3c23f6 | |||
| b22375bd21 | |||
| f9550c99bb | |||
| df369c7149 | |||
| 6868e68145 | |||
| 44e43d9479 | |||
| 079a8e3ec1 | |||
| 6ddb064c9e | |||
| 9b8b1b2532 | |||
| aba4bc2909 | |||
| f6d2b55f75 |
@@ -38,8 +38,19 @@ $config['SIGNATUR_CHECK_PAABGABETYPEN'] = ['end'];
|
||||
|
||||
// to be used as "https://moodle.technikum-wien.at/course/view.php?idnumber=dl{$stg_kz}" for stg specific moodle routing
|
||||
$config['STG_MOODLE_LINK'] = 'https://moodle.technikum-wien.at/course/view.php?idnumber=dl';
|
||||
// TODO: check if these links change if the file changes and how to better retrieve the link?
|
||||
$config['SIGNATUR_INFO_LINK_GERMAN'] = 'https://cis.technikum-wien.at/cms/dms.php?id=214779';
|
||||
$config['SIGNATUR_INFO_LINK_ENGLISH'] = 'https://cis.technikum-wien.at/cms/dms.php?id=264256';
|
||||
|
||||
$config['ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'] = true;
|
||||
$config['ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'] = true;
|
||||
|
||||
$config['BETREUER_SAMMELMAIL_BUTTON_STUDENT'] = true;
|
||||
|
||||
$config['MULTIEDIT_TABLE'] = true;
|
||||
|
||||
$config['STUDENT_EDIT_PROJEKTARBEIT_TITLE'] = true;
|
||||
|
||||
$config['CONFETTI_ON_ENDUPLOAD'] = true;
|
||||
|
||||
|
||||
|
||||
@@ -35,9 +35,12 @@ class Abgabe extends FHCAPI_Controller
|
||||
'getStudentProjektabgaben' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw', 'basis/abgabe_lektor:rw'),
|
||||
'postStudentProjektarbeitZwischenabgabe' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw'),
|
||||
'postStudentProjektarbeitEndupload' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw'),
|
||||
'postStudentProjektarbeitTitel' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw'),
|
||||
'getMitarbeiterProjektarbeiten' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
|
||||
'postProjektarbeitAbgabe' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
|
||||
'patchProjektarbeitAbgabeMultiple' => array('basis/abgabe_assistenz:rw'),
|
||||
'deleteProjektarbeitAbgabe' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
|
||||
'deleteProjektarbeitAbgabeMultiple' => array('basis/abgabe_assistenz:rw'),
|
||||
'postSerientermin' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
|
||||
'fetchDeadlines' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
|
||||
'getPaAbgabetypen' => self::PERM_LOGGED,
|
||||
@@ -90,6 +93,7 @@ class Abgabe extends FHCAPI_Controller
|
||||
$ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT');
|
||||
$ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER');
|
||||
$BETREUER_SAMMELMAIL_BUTTON_STUDENT = $this->config->item('BETREUER_SAMMELMAIL_BUTTON_STUDENT');
|
||||
$MULTIEDIT_TABLE = $this->config->item('MULTIEDIT_TABLE');
|
||||
|
||||
$ret = array(
|
||||
'old_abgabe_beurteilung_link' => $old_abgabe_beurteilung_link,
|
||||
@@ -97,7 +101,7 @@ class Abgabe extends FHCAPI_Controller
|
||||
'abgabetypenBetreuer' => $abgabetypenBetreuer,
|
||||
'ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT' => $ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT,
|
||||
'ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER' => $ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER,
|
||||
'BETREUER_SAMMELMAIL_BUTTON_STUDENT' => $BETREUER_SAMMELMAIL_BUTTON_STUDENT,
|
||||
'MULTIEDIT_TABLE' => $MULTIEDIT_TABLE,
|
||||
);
|
||||
|
||||
$this->terminateWithSuccess($ret);
|
||||
@@ -107,10 +111,18 @@ class Abgabe extends FHCAPI_Controller
|
||||
* loads config related to abgabetool for students to avoid handing out links reserved for employees
|
||||
*/
|
||||
public function getConfigStudent() {
|
||||
$moodle_link =$this->config->item('STG_MOODLE_LINK');
|
||||
|
||||
$moodle_link = $this->config->item('STG_MOODLE_LINK');
|
||||
$title_edit_allowed = $this->config->item('STUDENT_EDIT_PROJEKTARBEIT_TITLE');
|
||||
$confetti_on_endupload = $this->config->item('CONFETTI_ON_ENDUPLOAD');
|
||||
$siginfolink_german = $this->config->item('SIGNATUR_INFO_LINK_GERMAN');
|
||||
$siginfolink_english = $this->config->item('SIGNATUR_INFO_LINK_ENGLISH');
|
||||
|
||||
$ret = array(
|
||||
'moodle_link' => $moodle_link,
|
||||
'title_edit_allowed' => $title_edit_allowed,
|
||||
'confetti_on_endupload' => $confetti_on_endupload,
|
||||
'siginfolink_german' => $siginfolink_german,
|
||||
'siginfolink_english' => $siginfolink_english
|
||||
);
|
||||
|
||||
$this->terminateWithSuccess($ret);
|
||||
@@ -188,7 +200,7 @@ class Abgabe extends FHCAPI_Controller
|
||||
$result = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer(getAuthUID());
|
||||
}
|
||||
|
||||
$projektarbeiten = getData($result);
|
||||
$projektarbeiten = hasData($result) ? getData($result) : array();
|
||||
|
||||
if(count($projektarbeiten)) {
|
||||
foreach($projektarbeiten as $pa) {
|
||||
@@ -448,7 +460,218 @@ class Abgabe extends FHCAPI_Controller
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* POST METHOD
|
||||
* allows a student (or assistenz on their behalf) to update the titel of their own projektarbeit.
|
||||
* blocked once the projektarbeit has been graded (checkProjektarbeitForFinishedStatus).
|
||||
*/
|
||||
public function postStudentProjektarbeitTitel()
|
||||
{
|
||||
if(!$this->config->item('STUDENT_EDIT_PROJEKTARBEIT_TITLE')) {
|
||||
$this->terminateWithError($this->p->t('global', 'c4studentEditNotAllowed'), 'general');
|
||||
};
|
||||
|
||||
$projektarbeit_id = $this->input->post('projektarbeit_id');
|
||||
$titel = $this->input->post('titel');
|
||||
|
||||
if ($projektarbeit_id === NULL || trim((string)$projektarbeit_id) === ''
|
||||
|| $titel === NULL || trim((string)$titel) === '') {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
// strip all HTML tags to prevent XSS in mail bodies, table views and Projektarbeitsbenotung
|
||||
$titel = trim(strip_tags($titel));
|
||||
if ($titel === '') {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
// Reject emojis and pictographs
|
||||
// allows foreign letters, math symbols, accents, and standard punctuation.
|
||||
$emojiPattern = '/[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F900}-\x{1FAFF}\x{23E9}-\x{23EF}\x{2b50}\x{2700}-\x{27BF}]/u';
|
||||
|
||||
// i would like this much more but our server does not recognize this utf-8 character range this way, so hexcodes it is
|
||||
// if (preg_match('/\p{Extended_Pictographic}/u', $titel)) {
|
||||
if (preg_match($emojiPattern, $titel)) {
|
||||
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
$this->checkProjektarbeitForFinishedStatus($projektarbeit_id);
|
||||
|
||||
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
|
||||
|
||||
// Verify the projektarbeit actually belongs to the supplied student_uid
|
||||
$res = $this->ProjektarbeitModel->getStudentInfoForProjektarbeitId($projektarbeit_id);
|
||||
if (isError($res) || !hasData($res)) {
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4projektarbeitNichtGefunden'), 'general');
|
||||
}
|
||||
$assignedStudentUid = getData($res)[0]->uid;
|
||||
|
||||
// A student may only update their own title; assistenz is covered by checkZuordnung
|
||||
$zugeordnet = $this->checkZuordnung($projektarbeit_id, getAuthUID());
|
||||
if (getAuthUID() !== $assignedStudentUid && !$zugeordnet) {
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4noZuordnungBetreuerStudent'), 'general');
|
||||
}
|
||||
|
||||
|
||||
|
||||
$result = $this->ProjektarbeitModel->load($projektarbeit_id);
|
||||
$data = getData($result);
|
||||
|
||||
$oldTitle = $data[0]->titel ?? '';
|
||||
|
||||
$result = $this->ProjektarbeitModel->update(
|
||||
$projektarbeit_id,
|
||||
array(
|
||||
'titel' => $titel,
|
||||
'updatevon' => getAuthUID(),
|
||||
'updateamum' => date('Y-m-d H:i:s')
|
||||
)
|
||||
);
|
||||
|
||||
$this->getDataOrTerminateWithError($result, 'general');
|
||||
|
||||
$this->logLib->logInfoDB(array(
|
||||
'titelUpdate',
|
||||
array(
|
||||
'projektarbeit_id' => $projektarbeit_id,
|
||||
'titel' => $titel,
|
||||
'updatevon' => getAuthUID(),
|
||||
'updateamum' => date('Y-m-d H:i:s')
|
||||
),
|
||||
getAuthUID(),
|
||||
getAuthPersonId()
|
||||
));
|
||||
|
||||
$this->sendTitelChangedEmail(
|
||||
$projektarbeit_id,
|
||||
$titel,
|
||||
$oldTitle,
|
||||
$assignedStudentUid
|
||||
);
|
||||
|
||||
$result = $this->ProjektarbeitModel->load($projektarbeit_id);
|
||||
$titel = hasData($result) ? getData($result)[0]->titel : $titel;
|
||||
$this->terminateWithSuccess($titel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all betreuer of a projektarbeit and all assistenzen responsible for its studiengang
|
||||
* when a student updates the titel of their projektarbeit.
|
||||
*
|
||||
* Betreuer retrieval mirrors AbgabetoolJob->notifyBetreuerAboutChangedAbgaben.
|
||||
* Assistenz retrieval mirrors AbgabetoolJob->notifyAssistenzAboutChangedAbgaben.
|
||||
*
|
||||
* @param int $projektarbeit_id
|
||||
* @param string $new_titel The titel as it was saved
|
||||
* @param string $student_uid
|
||||
*/
|
||||
private function sendTitelChangedEmail($projektarbeit_id, $new_titel, $old_titel, $student_uid)
|
||||
{
|
||||
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
|
||||
$this->load->model('education/Projektbetreuer_model', 'ProjektbetreuerModel');
|
||||
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
|
||||
$this->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel');
|
||||
$this->load->model('person/Person_model', 'PersonModel');
|
||||
|
||||
$this->load->model('person/Person_model', 'PersonModel');
|
||||
$studentNameResult = $this->PersonModel->getFullName($student_uid);
|
||||
$studentFullName = hasData($studentNameResult) ? getData($studentNameResult) : $student_uid;
|
||||
|
||||
$studentInfoResult = $this->ProjektarbeitModel->getStudentInfoForProjektarbeitId($projektarbeit_id);
|
||||
if (isError($studentInfoResult) || !hasData($studentInfoResult)) {
|
||||
$this->logLib->logInfoDB(array('sendTitelChangedEmail: student info not found', $projektarbeit_id));
|
||||
return;
|
||||
}
|
||||
$studentInfo = getData($studentInfoResult)[0];
|
||||
$studiengang_kz = $studentInfo->studiengang_kz;
|
||||
|
||||
$stgResult = $this->StudiengangModel->load($studiengang_kz);
|
||||
$oe_kurzbz = null;
|
||||
if (!isError($stgResult) && hasData($stgResult)) {
|
||||
$oe_kurzbz = getData($stgResult)[0]->oe_kurzbz ?? null;
|
||||
}
|
||||
|
||||
// build shared mail data
|
||||
$base_mail_data = array(
|
||||
'studentFullName' => $studentFullName,
|
||||
'new_titel' => $new_titel,
|
||||
'old_titel' => $old_titel
|
||||
);
|
||||
|
||||
// notify all betreuer
|
||||
$betreuerResult = $this->ProjektbetreuerModel->getAllBetreuerOfProjektarbeit($projektarbeit_id);
|
||||
if (!isError($betreuerResult) && hasData($betreuerResult)) {
|
||||
|
||||
$linkAbgabetool = APP_ROOT . $this->config->item('URL_MITARBEITER');
|
||||
|
||||
foreach (getData($betreuerResult) as $betreuer) {
|
||||
$email = $betreuer->uid ? $betreuer->uid . '@' . DOMAIN : ($betreuer->private_email ?? null);
|
||||
if (!$email) {
|
||||
$this->logLib->logInfoDB(array('sendTitelChangedEmail: no email for betreuer', $betreuer->person_id));
|
||||
continue;
|
||||
}
|
||||
|
||||
$anredeResult = $this->ProjektarbeitModel->getProjektbetreuerAnrede($betreuer->person_id);
|
||||
$anrede = (!isError($anredeResult) && hasData($anredeResult)) ? getData($anredeResult)[0] : null;
|
||||
|
||||
$mail_data = array_merge($base_mail_data, array(
|
||||
'anredeFillString' => ($anrede->anrede ?? '') === 'Herr' ? 'r' : '',
|
||||
'anrede' => $anrede->anrede ?? '',
|
||||
'fullFormattedNameString' => $anrede->first ?? $email,
|
||||
'linkAbgabetool' => $linkAbgabetool,
|
||||
));
|
||||
|
||||
sendSanchoMail(
|
||||
'PATitleUpdated',
|
||||
$mail_data,
|
||||
$email,
|
||||
$this->p->t('abgabetool', 'c4PATitleChanged')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// notify assistenz for the studiengang OE
|
||||
if (!$oe_kurzbz) {
|
||||
$this->logLib->logInfoDB(array('sendTitelChangedEmail: no oe_kurzbz resolved, skipping assistenz', $studiengang_kz));
|
||||
return;
|
||||
}
|
||||
|
||||
$assistenzResult = $this->OrganisationseinheitModel->getAssistenzForOE($oe_kurzbz);
|
||||
if (isError($assistenzResult) || !hasData($assistenzResult)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$linkAbgabetool = APP_ROOT . $this->config->item('URL_ASSISTENZ');
|
||||
|
||||
// similar pattern as job uses via the assistenzMap
|
||||
$sentTo = [];
|
||||
foreach (getData($assistenzResult) as $assistenz) {
|
||||
if (in_array($assistenz->person_id, $sentTo)) {
|
||||
continue;
|
||||
}
|
||||
$sentTo[] = $assistenz->person_id;
|
||||
|
||||
$email = $assistenz->uid . '@' . DOMAIN;
|
||||
|
||||
$mail_data = array_merge($base_mail_data, array(
|
||||
'anredeFillString' => $assistenz->anrede === 'Herr' ? 'r' : '',
|
||||
'anrede' => $assistenz->anrede ?? '',
|
||||
'fullFormattedNameString' => $assistenz->first ?? ($assistenz->uid . '@' . DOMAIN),
|
||||
'linkAbgabetool' => $linkAbgabetool,
|
||||
));
|
||||
|
||||
sendSanchoMail(
|
||||
'PATitleUpdated',
|
||||
$mail_data,
|
||||
$email,
|
||||
$this->p->t('abgabetool', 'c4PATitleChanged')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// validate paabgabe deadline against servertime just in case a student spoofs their local clock and thus
|
||||
// unlocks the upload ui
|
||||
private function checkPaabgabeDeadline($paabgabe_id) {
|
||||
@@ -687,6 +910,99 @@ class Abgabe extends FHCAPI_Controller
|
||||
$this->terminateWithSuccess([$paabgabe, $existingPaabgabe]);
|
||||
}
|
||||
|
||||
/**
|
||||
* called by abgabetool/assistenz when bulk-editing multiple abgabetermine via the flat termine table view
|
||||
* only fields present in the payload are updated - absent fields are left untouched
|
||||
*/
|
||||
public function patchProjektarbeitAbgabeMultiple() {
|
||||
$paabgabe_ids = $this->input->post('paabgabe_ids');
|
||||
|
||||
if ($paabgabe_ids === NULL || !is_array($paabgabe_ids) || count($paabgabe_ids) === 0) {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
// collect only fields that were actually sent
|
||||
$updateFields = [];
|
||||
|
||||
$datum = $this->input->post('datum');
|
||||
if ($datum !== NULL && trim((string)$datum) !== '') {
|
||||
$updateFields['datum'] = $datum;
|
||||
}
|
||||
|
||||
$paabgabetyp_kurzbz = $this->input->post('paabgabetyp_kurzbz');
|
||||
if ($paabgabetyp_kurzbz !== NULL && trim((string)$paabgabetyp_kurzbz) !== '') {
|
||||
$updateFields['paabgabetyp_kurzbz'] = $paabgabetyp_kurzbz;
|
||||
}
|
||||
|
||||
$kurzbz = $this->input->post('kurzbz');
|
||||
if ($kurzbz !== NULL) {
|
||||
$updateFields['kurzbz'] = $kurzbz;
|
||||
}
|
||||
|
||||
// booleans: only include if explicitly posted
|
||||
$upload_allowed = $this->input->post('upload_allowed');
|
||||
if ($upload_allowed !== NULL) {
|
||||
$updateFields['upload_allowed'] = filter_var($upload_allowed, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
$fixtermin = $this->input->post('fixtermin');
|
||||
if ($fixtermin !== NULL) {
|
||||
$updateFields['fixtermin'] = filter_var($fixtermin, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
if (empty($updateFields)) {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
|
||||
|
||||
$results = [];
|
||||
foreach ($paabgabe_ids as $paabgabe_id) {
|
||||
$paabgabe_id = trim((string)$paabgabe_id);
|
||||
|
||||
if ($paabgabe_id === '') {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
$projektarbeit_id = $this->getProjektarbeitIDForPaabgabeID($paabgabe_id);
|
||||
|
||||
$this->checkProjektarbeitForFinishedStatus($projektarbeit_id);
|
||||
|
||||
$zugeordnet = $this->checkZuordnung($projektarbeit_id, getAuthUID());
|
||||
if (!$zugeordnet) {
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4noZuordnungBetreuerStudent'), 'general');
|
||||
}
|
||||
|
||||
$paabgabeResult = $this->PaabgabeModel->load($paabgabe_id);
|
||||
$paabgabeArr = $this->getDataOrTerminateWithError($paabgabeResult, 'general');
|
||||
|
||||
if (count($paabgabeArr) === 0) {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
$result = $this->PaabgabeModel->update(
|
||||
$paabgabe_id,
|
||||
array_merge($updateFields, [
|
||||
'updatevon' => getAuthUID(),
|
||||
'updateamum' => date('Y-m-d H:i:s')
|
||||
])
|
||||
);
|
||||
|
||||
$this->getDataOrTerminateWithError($result, 'general');
|
||||
$results[] = getData($this->PaabgabeModel->load($paabgabe_id))[0];
|
||||
|
||||
$this->logLib->logInfoDB(array(
|
||||
'paabgabe bulk updated',
|
||||
$paabgabe_id,
|
||||
$updateFields,
|
||||
getAuthUID(),
|
||||
getAuthPersonId()
|
||||
));
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* called by abgabetool/mitarbeiter in mitarbeiterdetail.js when deleting an abgabetermin
|
||||
* deletion is only possible if user is assistenz OR betreuer deletes their own custom termin
|
||||
@@ -719,11 +1035,55 @@ class Abgabe extends FHCAPI_Controller
|
||||
$result = $this->PaabgabeModel->delete($paabgabe_id);
|
||||
$result = $this->getDataOrTerminateWithError($result, 'general');
|
||||
|
||||
// TODO: consider this in nightly email job
|
||||
$this->logLib->logInfoDB(array($paabgabeArr[0], getAuthUID(), getAuthPersonId()));
|
||||
$this->terminateWithSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* called by abgabetool/assistenz when deleting multiple abgabetermine via the flat termine table view
|
||||
*/
|
||||
public function deleteProjektarbeitAbgabeMultiple() {
|
||||
$paabgabe_ids = $this->input->post('paabgabe_ids');
|
||||
|
||||
if ($paabgabe_ids === NULL || !is_array($paabgabe_ids) || count($paabgabe_ids) === 0) {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
|
||||
|
||||
$results = [];
|
||||
foreach ($paabgabe_ids as $paabgabe_id) {
|
||||
$paabgabe_id = trim((string)$paabgabe_id);
|
||||
|
||||
if ($paabgabe_id === '') {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
$this->checkProjektarbeitForFinishedStatus($this->getProjektarbeitIDForPaabgabeID($paabgabe_id));
|
||||
|
||||
$zugeordnet = $this->checkZuordnungByPaabgabe($paabgabe_id, getAuthUID());
|
||||
|
||||
if (!$zugeordnet) {
|
||||
$this->terminateWithError($this->p->t('abgabetool', 'c4noZuordnungBetreuerStudent'), 'general');
|
||||
}
|
||||
|
||||
$paabgabeResult = $this->PaabgabeModel->load($paabgabe_id);
|
||||
$paabgabeArr = $this->getDataOrTerminateWithError($paabgabeResult, 'general');
|
||||
|
||||
if (count($paabgabeArr) == 0) {
|
||||
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
|
||||
}
|
||||
|
||||
$result = $this->PaabgabeModel->delete($paabgabe_id);
|
||||
$result = $this->getDataOrTerminateWithError($result, 'general');
|
||||
$results[] = $result;
|
||||
|
||||
$this->logLib->logInfoDB(array($paabgabeArr[0], getAuthUID(), getAuthPersonId()));
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* endpoint for adding the same paabgabe for multiple projektarbeiten
|
||||
* can be slow for large n since it queries twice per projektarbeit_id
|
||||
@@ -1206,7 +1566,7 @@ class Abgabe extends FHCAPI_Controller
|
||||
};
|
||||
Events::trigger('projektarbeit_is_current', $projektarbeit_id, $returnFunc);
|
||||
if(!$projektarbeitIsCurrent) {
|
||||
$this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeit'), 'general');
|
||||
$this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv2'), 'general');
|
||||
}
|
||||
|
||||
// Link to Abgabetool
|
||||
@@ -1411,8 +1771,14 @@ class Abgabe extends FHCAPI_Controller
|
||||
|
||||
$data = getData($res)[0];
|
||||
if($data->note !== NULL) {
|
||||
$this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeit'), 'general');
|
||||
// hardcode this error msg cause phrasen arent reliable and people keep bugging why the cant edit old entries they definitely shouldnt update
|
||||
$message = $this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv2');
|
||||
if(strpos($message, "<<") === 0) { // phrase could not be loaded
|
||||
$this->terminateWithError('Die Projektarbeit wurde bereits benotet, Sie dürfen deshalb keine weiteren Termine anlegen oder bearbeiten.', 'general');
|
||||
} else {
|
||||
$this->terminateWithError($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,15 @@ class Projektarbeit extends FHCAPI_Controller
|
||||
|
||||
if (!isset($projektarbeit_id) || !is_numeric($projektarbeit_id)) return $this->terminateWithError('Projektarbeit Id missing', self::ERROR_TYPE_GENERAL);
|
||||
|
||||
$result = $this->fetchProjektarbeitByID($projektarbeit_id);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess(current($data));
|
||||
}
|
||||
|
||||
private function fetchProjektarbeitById($projektarbeit_id) {
|
||||
$this->ProjektarbeitModel->resetQuery();
|
||||
$this->ProjektarbeitModel->addSelect(
|
||||
'lehre.tbl_projektarbeit.projektarbeit_id, titel, titel_english, themenbereich, projekttyp_kurzbz, lehrveranstaltung_id, lehreinheit_id,
|
||||
firma_id, beginn, ende, gesperrtbis, note, final, freigegeben, tbl_projektarbeit.anmerkung, fa.name AS firma_name'
|
||||
@@ -97,13 +106,10 @@ class Projektarbeit extends FHCAPI_Controller
|
||||
$this->ProjektarbeitModel->addJoin('lehre.tbl_lehreinheit le', 'lehreinheit_id');
|
||||
$this->ProjektarbeitModel->addJoin('lehre.tbl_lehrveranstaltung lv', 'lehrveranstaltung_id');
|
||||
$this->ProjektarbeitModel->addJoin('public.tbl_firma fa', 'firma_id', 'LEFT');
|
||||
$result = $this->ProjektarbeitModel->loadWhere(
|
||||
return $this->ProjektarbeitModel->loadWhere(
|
||||
array('projektarbeit_id' => $projektarbeit_id)
|
||||
);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$this->terminateWithSuccess(current($data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +138,8 @@ class Projektarbeit extends FHCAPI_Controller
|
||||
);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($result);
|
||||
|
||||
$data = $this->getDataOrTerminateWithError($this->fetchProjektarbeitById($data));
|
||||
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
|
||||
|
||||
@@ -358,7 +358,8 @@ class AbgabetoolJob extends JOB_Controller
|
||||
foreach($assistenzMap as $assistenz_person_id => $tupelArr) {
|
||||
|
||||
$abgabenString = '<div style="font-family: Arial, sans-serif; color: #333;">';
|
||||
|
||||
$hasContent = false;
|
||||
|
||||
foreach($tupelArr as $tupel) {
|
||||
$projektarbeit_id = $tupel[0];
|
||||
$assistenzRow = $tupel[1];
|
||||
@@ -377,6 +378,7 @@ class AbgabetoolJob extends JOB_Controller
|
||||
if(count($relevantAbgaben) == 0) {
|
||||
continue;
|
||||
}
|
||||
$hasContent = true;
|
||||
|
||||
// Format the Student Name
|
||||
$s = $relevantAbgaben[0];
|
||||
@@ -447,7 +449,12 @@ class AbgabetoolJob extends JOB_Controller
|
||||
}
|
||||
|
||||
$abgabenString .= '</div>';
|
||||
|
||||
|
||||
// skip send entirely
|
||||
if (!$hasContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// done with building the change list, now send it
|
||||
$assistenzRow = $tupelArr[0][1];
|
||||
$anrede = $assistenzRow->anrede;
|
||||
|
||||
@@ -360,7 +360,10 @@ class StundenplanLib
|
||||
if (isError($ort_content_object)) {
|
||||
return error(getData($ort_content_object));
|
||||
}
|
||||
$ort_content_object = getData($ort_content_object)[0];
|
||||
$ort_content_object_data = getData($ort_content_object);
|
||||
$ort_content_object = (is_array($ort_content_object_data) && count($ort_content_object_data) > 0)
|
||||
? $ort_content_object_data[0]
|
||||
: null;
|
||||
if($ort_content_object) {
|
||||
$item->ort_content_id = $ort_content_object->content_id;
|
||||
}
|
||||
|
||||
@@ -341,172 +341,130 @@ class Projektarbeit_model extends DB_Model
|
||||
|
||||
|
||||
public function getProjektarbeitenForStudiengang($studiengang_kz, $benotet) {
|
||||
$new_qry = "SELECT DISTINCT ON(tmp.projektarbeit_id) *, campus.get_betreuer_details(tmp.zweitbetreuer_person_id) as zweitbetreuer_full_name, campus.get_betreuer_details(tmp.betreuer_person_id) as erstbetreuer_full_name
|
||||
FROM(
|
||||
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.titelpre as betreuer_titelpre,
|
||||
betreuer_person.vorname as betreuer_vorname,
|
||||
betreuer_person.nachname as betreuer_nachname,
|
||||
betreuer_person.titelpost as betreuer_titelpost,
|
||||
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,
|
||||
lehre.tbl_projektarbeit.note as note,
|
||||
(
|
||||
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', 'Senatsmitglied')
|
||||
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', 'Senatsmitglied')
|
||||
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', 'Senatsmitglied')
|
||||
LIMIT 1
|
||||
)
|
||||
as zweitbetreuer_full_name,
|
||||
(
|
||||
SELECT titelpre
|
||||
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', 'Senatsmitglied')
|
||||
LIMIT 1
|
||||
)
|
||||
as zweitbetreuer_titelpre,
|
||||
(
|
||||
SELECT vorname
|
||||
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', 'Senatsmitglied')
|
||||
LIMIT 1
|
||||
)
|
||||
as zweitbetreuer_vorname,
|
||||
(
|
||||
SELECT nachname
|
||||
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', 'Senatsmitglied')
|
||||
LIMIT 1
|
||||
)
|
||||
as zweitbetreuer_nachname,
|
||||
(
|
||||
SELECT 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', 'Senatsmitglied')
|
||||
LIMIT 1
|
||||
)
|
||||
as zweitbetreuer_titelpost,
|
||||
(
|
||||
SELECT
|
||||
COALESCE(tbl_studienplan.orgform_kurzbz,
|
||||
tbl_prestudentstatus.orgform_kurzbz, tbl_studiengang.orgform_kurzbz) as
|
||||
orgform
|
||||
FROM
|
||||
public.tbl_prestudent
|
||||
JOIN public.tbl_prestudentstatus USING(prestudent_id)
|
||||
JOIN public.tbl_studiensemester USING(studiensemester_kurzbz)
|
||||
JOIN public.tbl_studiengang USING(studiengang_kz)
|
||||
LEFT JOIN lehre.tbl_studienplan USING(studienplan_id)
|
||||
WHERE
|
||||
prestudent_id=tbl_student.prestudent_id
|
||||
ORDER BY tbl_prestudentstatus.datum DESC LIMIT 1
|
||||
) as orgform,
|
||||
(SELECT status_kurzbz FROM public.tbl_prestudentstatus
|
||||
WHERE prestudent_id=tbl_student.prestudent_id
|
||||
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as studienstatus
|
||||
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'
|
||||
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Betreuer'
|
||||
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbetreuer'
|
||||
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Senatsvorsitz'
|
||||
)
|
||||
AND public.tbl_studiengang.studiengang_kz = ?";
|
||||
$new_qry = "WITH secondary_betreuer AS (
|
||||
SELECT DISTINCT ON (pb.projektarbeit_id)
|
||||
pb.projektarbeit_id,
|
||||
pb.person_id AS zweitbetreuer_person_id,
|
||||
pb.betreuerart_kurzbz AS zweitbetreuer_betreuerart_kurzbz,
|
||||
ba.beschreibung AS zweitbetreuer_betreuerart_beschreibung,
|
||||
p.titelpre AS zweitbetreuer_titelpre,
|
||||
p.vorname AS zweitbetreuer_vorname,
|
||||
p.nachname AS zweitbetreuer_nachname,
|
||||
p.titelpost AS zweitbetreuer_titelpost,
|
||||
trim(
|
||||
COALESCE(p.titelpre, '') || ' ' ||
|
||||
COALESCE(p.vorname, '') || ' ' ||
|
||||
COALESCE(p.nachname, '') || ' ' ||
|
||||
COALESCE(p.titelpost, '')
|
||||
) AS zweitbetreuer_full_name
|
||||
FROM lehre.tbl_projektbetreuer pb
|
||||
JOIN public.tbl_person p ON p.person_id = pb.person_id
|
||||
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
|
||||
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
|
||||
WHERE pb.betreuerart_kurzbz = ANY('{Zweitbetreuer,Zweitbegutachter,Senatsmitglied}')
|
||||
ORDER BY pb.projektarbeit_id -- DISTINCT ON needs this to be deterministic
|
||||
)
|
||||
|
||||
if($benotet == 0) {
|
||||
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NULL ";
|
||||
} else if ($benotet == 1) {
|
||||
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NOT NULL ";
|
||||
}
|
||||
|
||||
$new_qry .= " ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
|
||||
SELECT DISTINCT ON (tmp.projektarbeit_id)
|
||||
*,
|
||||
campus.get_betreuer_details(tmp.zweitbetreuer_person_id) AS zweitbetreuer_full_name,
|
||||
campus.get_betreuer_details(tmp.betreuer_person_id) AS erstbetreuer_full_name
|
||||
FROM (
|
||||
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,
|
||||
public.tbl_student.matrikelnr,
|
||||
tbl_lehreinheit.studiensemester_kurzbz,
|
||||
betreuer_benutzer.uid AS betreuer_benutzer_uid,
|
||||
betreuer_person.titelpre AS betreuer_titelpre,
|
||||
betreuer_person.vorname AS betreuer_vorname,
|
||||
betreuer_person.nachname AS betreuer_nachname,
|
||||
betreuer_person.titelpost AS betreuer_titelpost,
|
||||
lehre.tbl_projektbetreuer.betreuerart_kurzbz AS betreuerart,
|
||||
lehre.tbl_projektbetreuer.person_id AS betreuer_person_id,
|
||||
lehre.tbl_projektarbeit.sprache,
|
||||
lehre.tbl_projektarbeit.seitenanzahl,
|
||||
lehre.tbl_projektarbeit.kontrollschlagwoerter,
|
||||
lehre.tbl_projektarbeit.schlagwoerter,
|
||||
lehre.tbl_projektarbeit.schlagwoerter_en,
|
||||
lehre.tbl_projektarbeit.abstract,
|
||||
lehre.tbl_projektarbeit.abstract_en,
|
||||
lehre.tbl_projektarbeit.insertamum,
|
||||
lehre.tbl_projektarbeit.note,
|
||||
|
||||
sb.zweitbetreuer_person_id,
|
||||
sb.zweitbetreuer_betreuerart_kurzbz,
|
||||
sb.zweitbetreuer_betreuerart_beschreibung,
|
||||
sb.zweitbetreuer_full_name,
|
||||
sb.zweitbetreuer_titelpre,
|
||||
sb.zweitbetreuer_vorname,
|
||||
sb.zweitbetreuer_nachname,
|
||||
sb.zweitbetreuer_titelpost,
|
||||
|
||||
(
|
||||
SELECT orgform_kurzbz
|
||||
FROM public.tbl_prestudentstatus
|
||||
WHERE prestudent_id = (
|
||||
SELECT prestudent_id FROM public.tbl_student
|
||||
WHERE student_uid = student_benutzer.uid LIMIT 1
|
||||
)
|
||||
ORDER BY datum DESC, insertamum DESC, ext_id DESC
|
||||
LIMIT 1
|
||||
) AS organisationsform,
|
||||
(
|
||||
SELECT COALESCE(tbl_studienplan.orgform_kurzbz,
|
||||
tbl_prestudentstatus.orgform_kurzbz,
|
||||
tbl_studiengang.orgform_kurzbz)
|
||||
FROM public.tbl_prestudent
|
||||
JOIN public.tbl_prestudentstatus USING (prestudent_id)
|
||||
JOIN public.tbl_studiensemester USING (studiensemester_kurzbz)
|
||||
JOIN public.tbl_studiengang USING (studiengang_kz)
|
||||
LEFT JOIN lehre.tbl_studienplan USING (studienplan_id)
|
||||
WHERE prestudent_id = public.tbl_student.prestudent_id
|
||||
ORDER BY tbl_prestudentstatus.datum DESC
|
||||
LIMIT 1
|
||||
) AS orgform,
|
||||
(
|
||||
SELECT status_kurzbz
|
||||
FROM public.tbl_prestudentstatus
|
||||
WHERE prestudent_id = public.tbl_student.prestudent_id
|
||||
ORDER BY datum DESC, insertamum DESC, ext_id DESC
|
||||
LIMIT 1
|
||||
) AS studienstatus
|
||||
|
||||
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
|
||||
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id -- ← THE NEW LINE
|
||||
|
||||
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
|
||||
AND student_benutzer.aktiv
|
||||
AND lehre.tbl_projektbetreuer.betreuerart_kurzbz IN (
|
||||
'Erstbegutachter', 'Begutachter', 'Betreuer', 'Erstbetreuer', 'Senatsvorsitz'
|
||||
)
|
||||
AND public.tbl_studiengang.studiengang_kz = ?";
|
||||
|
||||
if($benotet == 0) {
|
||||
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NULL ";
|
||||
} else if ($benotet == 1) {
|
||||
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NOT NULL ";
|
||||
}
|
||||
|
||||
$new_qry .= " ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
|
||||
) as tmp";
|
||||
|
||||
return $this->execReadOnlyQuery($new_qry, array($studiengang_kz));
|
||||
|
||||
@@ -38,7 +38,7 @@ $includesArray = array(
|
||||
|
||||
$this->load->view('templates/FHC-Header', $includesArray);
|
||||
?>
|
||||
<div id="abgabetoolroot" class="h-100" style="max-width: 95%;" route=<?php echo json_encode($route) ?>
|
||||
<div id="abgabetoolroot" class="h-100" style="max-width: 99%" route=<?php echo json_encode($route) ?>
|
||||
uid=<?php echo $uid ?>
|
||||
student_uid_prop="<?php echo $student_uid_prop ?? '' ?>"
|
||||
stg_kz_prop="<?php echo $stg_kz_prop ?? '' ?>"
|
||||
|
||||
@@ -277,3 +277,7 @@ html.fs_huge {
|
||||
}
|
||||
*/
|
||||
/* slim ende */
|
||||
|
||||
.fhc-xxl-modal {
|
||||
min-width: 80vw;
|
||||
}
|
||||
@@ -305,4 +305,94 @@
|
||||
/* If you use hover rows, you need to ensure the sticky cell matches the hover color */
|
||||
#abgabetable .tabulator-row:hover .tabulator-cell.sticky-col {
|
||||
background-color: #ccc; /* Match your existing hover color */
|
||||
}
|
||||
}
|
||||
|
||||
.tabulator-cell {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.tabulator-col-title {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
@container (max-width: 100px) {
|
||||
.full-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.short-text {
|
||||
display: inline-block !important;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/*conditional tooltips fix*/
|
||||
.p-tooltip.custom-tooltip {
|
||||
z-index: 8001 !important;
|
||||
}
|
||||
|
||||
/* Shrinks font and table rows for desktop users who have zoomed in their browser (150%+).
|
||||
Does not affect mobile/touchscreen devices, which use touch input instead of a mouse. */
|
||||
@media (pointer: fine) and (min-resolution: 1.5dppx) {
|
||||
|
||||
html.abgabetool {
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
|
||||
.abgabetool .tabulator-cell,
|
||||
.abgabetool .tabulator-row {
|
||||
height: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*confetti celebration on endupload - impossible to miss*/
|
||||
#confetti-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.confetti-piece {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
will-change: top, transform, opacity;
|
||||
}
|
||||
|
||||
/* Background Rain */
|
||||
@keyframes fallAndSpin {
|
||||
0% {
|
||||
top: var(--start-y);
|
||||
transform: translate3d(0, 0, 0) rotateX(0deg) rotateY(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 105vh;
|
||||
transform: translate3d(var(--drift), 0, 0) rotateX(720deg) rotateY(360deg);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Corner Cannons*/
|
||||
@keyframes cannonBlast {
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0) scale(0.3) rotate(0deg);
|
||||
opacity: 1;
|
||||
animation-timing-function: cubic-bezier(0.1, 0.8, 0.2, 1);
|
||||
}
|
||||
30% {
|
||||
transform: translate3d(var(--blast-x), var(--blast-y), 0) scale(1.2) rotate(270deg);
|
||||
opacity: 1;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(calc(var(--blast-x) * 1.4), 15vh, 0) scale(0.4) rotate(630deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@ export default {
|
||||
}
|
||||
};
|
||||
},
|
||||
patchProjektarbeitAbgabeMultiple(payload) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/Abgabe/patchProjektarbeitAbgabeMultiple',
|
||||
params: payload
|
||||
};
|
||||
},
|
||||
deleteProjektarbeitAbgabe(paabgabe_id) {
|
||||
return {
|
||||
method: 'post',
|
||||
@@ -84,6 +91,13 @@ export default {
|
||||
params: { paabgabe_id }
|
||||
};
|
||||
},
|
||||
deleteProjektarbeitAbgabeMultiple(paabgabe_ids) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/Abgabe/deleteProjektarbeitAbgabeMultiple',
|
||||
params: { paabgabe_ids }
|
||||
};
|
||||
},
|
||||
postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, upload_allowed, projektarbeit_ids, fixtermin) {
|
||||
return {
|
||||
method: 'post',
|
||||
@@ -139,6 +153,14 @@ export default {
|
||||
url: '/api/frontend/v1/Abgabe/getSignaturStatusForProjektarbeitAbgaben',
|
||||
params: {paabgabe_ids, student_uid},
|
||||
|
||||
};
|
||||
},
|
||||
postStudentProjektarbeitTitel(projektarbeit_id, titel) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/Abgabe/postStudentProjektarbeitTitel',
|
||||
params: {projektarbeit_id, titel},
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -25,9 +25,6 @@ const app = Vue.createApp({
|
||||
|
||||
},
|
||||
computed: {
|
||||
viewData() {
|
||||
return { uid: this.uid}
|
||||
},
|
||||
student_uid_computed() {
|
||||
return this.student_uid ?? this.uid
|
||||
},
|
||||
@@ -55,10 +52,10 @@ const app = Vue.createApp({
|
||||
},
|
||||
template: `
|
||||
<template v-if="comp && uid">
|
||||
<AbgabetoolStudent v-if="comp == 'AbgabetoolStudent'" :viewData="viewData" :student_uid_prop="student_uid_computed"></AbgabetoolStudent>
|
||||
<AbgabetoolMitarbeiter v-if="comp == 'AbgabetoolMitarbeiter'" :viewData="viewData"></AbgabetoolMitarbeiter>
|
||||
<AbgabetoolAssistenz v-if="comp == 'AbgabetoolAssistenz'" :viewData="viewData" :stg_kz_prop="stg_kz_computed"></AbgabetoolAssistenz>
|
||||
<DeadlineOverview v-if="comp == 'DeadlinesOverview'" :viewData="viewData"></DeadlineOverview>
|
||||
<AbgabetoolStudent v-if="comp == 'AbgabetoolStudent'" :student_uid_prop="student_uid_computed"></AbgabetoolStudent>
|
||||
<AbgabetoolMitarbeiter v-if="comp == 'AbgabetoolMitarbeiter'"></AbgabetoolMitarbeiter>
|
||||
<AbgabetoolAssistenz v-if="comp == 'AbgabetoolAssistenz'" :stg_kz_prop="stg_kz_computed"></AbgabetoolAssistenz>
|
||||
<DeadlineOverview v-if="comp == 'DeadlinesOverview'"></DeadlineOverview>
|
||||
</template>
|
||||
`
|
||||
});
|
||||
|
||||
@@ -4,7 +4,9 @@ export default {
|
||||
name: 'BootstrapModal',
|
||||
data: () => ({
|
||||
modal: null,
|
||||
fullscreen: false
|
||||
fullscreen: false,
|
||||
expandBtnHovered: false,
|
||||
expandBtnFocused: false,
|
||||
}),
|
||||
props: {
|
||||
backdrop: {
|
||||
@@ -70,6 +72,29 @@ export default {
|
||||
this.$emit('toggleFullscreen')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getExpandButtonStyles() {
|
||||
const hovered = this.expandBtnHovered;
|
||||
const focused = this.expandBtnFocused;
|
||||
return `display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
font-size: 1em;
|
||||
opacity: 0.5;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
transition: opacity 0.15s ease;
|
||||
opacity: ${focused ? '1' : hovered ? '0.75' : '0.5'};
|
||||
outline: ${focused ? '1px solid currentColor' : 'none'};
|
||||
outline-offset: 2px;`
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.modal)
|
||||
this.modal = new bootstrap.Modal(this.$refs.modal, {
|
||||
@@ -140,9 +165,13 @@ export default {
|
||||
<div class="d-flex align-items-center ms-auto gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn mb-1"
|
||||
:style="getExpandButtonStyles"
|
||||
v-if="allowFullscreenExpand"
|
||||
@click="toggleFullscreen"
|
||||
@mouseenter="expandBtnHovered = true"
|
||||
@mouseleave="expandBtnHovered = false"
|
||||
@focusin="expandBtnFocused = true"
|
||||
@focusout="expandBtnFocused = false"
|
||||
:aria-label="fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'"
|
||||
>
|
||||
<i v-if="!fullscreen" class="fa-solid fa-expand"></i>
|
||||
|
||||
@@ -194,6 +194,9 @@ export default {
|
||||
v-else
|
||||
:is="renderers[event.type]?.calendarEvent"
|
||||
:event="event"
|
||||
:timeSlotDisplayBehavior="
|
||||
$props.mode.toLowerCase() === 'list' ? 'always' : 'default'
|
||||
"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -97,6 +97,7 @@ export default {
|
||||
<component
|
||||
:is="renderers[event.type]?.calendarEvent"
|
||||
:event="event"
|
||||
:timeSlotDisplayBehavior="'always'"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,6 +2,7 @@ import BsModal from '../../Bootstrap/Modal.js';
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
import ApiAbgabe from '../../../api/factory/abgabe.js'
|
||||
import { getDateStyleClass } from "./getDateStyleClass.js";
|
||||
import { compareISODateValues, formatISODate, getViennaTodayISO } from "./dateUtils.js";
|
||||
|
||||
export const AbgabeMitarbeiterDetail = {
|
||||
name: "AbgabeMitarbeiterDetail",
|
||||
@@ -46,12 +47,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
eidAkzeptiert: false,
|
||||
enduploadTermin: null,
|
||||
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
|
||||
speedDialItems: [{
|
||||
label: Vue.computed(() => this.$p.t('abgabetool/c4newAbgabetermin')),
|
||||
icon: "fa fa-plus",
|
||||
command: this.openCreateNewAbgabeModal,
|
||||
disabled: Vue.computed(() => !this.getAllowedToCreateNewTermin)
|
||||
},
|
||||
speedDialItems: [
|
||||
{
|
||||
label: Vue.computed(() => this.$p.t('abgabetool/c4benoten')),
|
||||
icon: "fa fa-user-check",
|
||||
@@ -81,6 +77,9 @@ export const AbgabeMitarbeiterDetail = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
terminIsInvalid(termin) {
|
||||
return termin.note?.positiv == false && !termin.beurteilungsnotiz
|
||||
},
|
||||
getNoteBezeichnung(termin){
|
||||
if(termin.noteBackend?.bezeichnung) {
|
||||
return termin.noteBackend?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
|
||||
@@ -113,6 +112,8 @@ export const AbgabeMitarbeiterDetail = {
|
||||
if(newTerminRes.note) {
|
||||
newTerminRes.note = noteOpt
|
||||
newTerminRes.noteBackend = noteOpt // certain UI elements should only reflect persisted state
|
||||
termin.allowedToDelete = false
|
||||
newTerminRes.allowedToDelete = false
|
||||
}
|
||||
newTerminRes.invertedFixtermin = !newTerminRes.fixtermin
|
||||
const existingTerminRes = res.data[1]
|
||||
@@ -132,13 +133,13 @@ export const AbgabeMitarbeiterDetail = {
|
||||
} else {
|
||||
const noteOptExisting = this.allowedNotenOptions.find(opt => opt.note == existingTerminRes.note)
|
||||
existingTerminRes.note = noteOptExisting
|
||||
|
||||
|
||||
termin.paabgabetyp_kurzbz = newTerminRes.paabgabetyp_kurzbz
|
||||
termin.noteBackend = noteOpt // do NOT take noteOptExisting -> should reflect the "yes the qgate grade is confirmed in backend ux behaviour"
|
||||
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
|
||||
}
|
||||
|
||||
this.projektarbeit.abgabetermine.sort((a, b) =>new Date(a.datum) - new Date(b.datum))
|
||||
this.projektarbeit.abgabetermine.sort((a, b) => compareISODateValues(a.datum, b.datum))
|
||||
|
||||
const index = this.projektarbeit.abgabetermine.findIndex(t => termin.paabgabe_id == t.paabgabe_id)
|
||||
|
||||
@@ -159,7 +160,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '', // todo kurzbz textfield value vorschlag für qualgates
|
||||
'datum': new Date().toISOString().split('T')[0],
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': false,
|
||||
@@ -337,16 +338,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
}
|
||||
},
|
||||
formatDate(dateParam) {
|
||||
// unsafe for datepickers, dont use there
|
||||
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}`
|
||||
return formatISODate(dateParam)
|
||||
},
|
||||
openCreateNewAbgabeModal() {
|
||||
if(this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') {
|
||||
@@ -364,7 +356,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '',
|
||||
'datum': new Date().toISOString().split('T')[0],
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': typ.upload_allowed_default,
|
||||
@@ -398,7 +390,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '',
|
||||
'datum': new Date().toISOString().split('T')[0],
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': false,
|
||||
@@ -446,7 +438,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
}
|
||||
},
|
||||
getMessagePtStyle() {
|
||||
// adjust outer spacing and internal padding to appear similar to doenload button in size
|
||||
// adjust outer spacing and internal padding to appear similar to download button in size
|
||||
return {
|
||||
root: {
|
||||
style: {
|
||||
@@ -561,6 +553,12 @@ export const AbgabeMitarbeiterDetail = {
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
},
|
||||
getTooltipBeurteilungsnotiz() {
|
||||
return {
|
||||
value: this.$p.t('abgabetool/c4beurteilungsnotizBeiNegNote'),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
},
|
||||
getProjektarbeitTitel() {
|
||||
if(this.projektarbeit?.titel) return this.$capitalize(this.$p.t('abgabetool/c4titel')) + ': ' + this.projektarbeit.titel
|
||||
|
||||
@@ -591,7 +589,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '',
|
||||
'datum': new Date().toISOString().split('T')[0],
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': typ.upload_allowed_default,
|
||||
@@ -627,6 +625,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
dialogClass="bordered-modal modal-lg"
|
||||
:backdrop="true"
|
||||
@hideBsModal="showAutomagicModalPhrase=false;"
|
||||
bodyClass="px-4 py-4"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<div>
|
||||
@@ -656,6 +655,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
:enable-time-picker="false"
|
||||
locale="de"
|
||||
format="dd.MM.yyyy"
|
||||
model-type="yyyy-MM-dd"
|
||||
:text-input="true"
|
||||
auto-apply>
|
||||
</VueDatePicker>
|
||||
@@ -806,6 +806,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
:enable-time-picker="false"
|
||||
locale="de"
|
||||
format="dd.MM.yyyy"
|
||||
model-type="yyyy-MM-dd"
|
||||
:text-input="true"
|
||||
auto-apply>
|
||||
</VueDatePicker>
|
||||
@@ -851,8 +852,10 @@ export const AbgabeMitarbeiterDetail = {
|
||||
</div>
|
||||
<div class="row mt-2" v-if="termin.bezeichnung?.benotbar">
|
||||
<div class="col-12 col-md-3 fw-bold align-content-center">{{$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 class="col-12 col-md-9" v-tooltip.right="terminIsInvalid(termin) && getTooltipBeurteilungsnotiz">
|
||||
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz"
|
||||
:class="{ 'p-invalid': terminIsInvalid(termin) }"
|
||||
rows="1" class="w-100" :disabled="!termin.allowedToSave"></Textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -874,6 +877,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
:disabled="true"
|
||||
locale="de"
|
||||
format="dd.MM.yyyy"
|
||||
model-type="yyyy-MM-dd"
|
||||
>
|
||||
</VueDatePicker>
|
||||
</div>
|
||||
@@ -889,9 +893,6 @@ export const AbgabeMitarbeiterDetail = {
|
||||
<Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $capitalize($p.t('abgabetool/c4keineSignatur')) }} </Message>
|
||||
<Message v-else-if="termin?.signatur == 'error'" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $capitalize($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>
|
||||
@@ -907,7 +908,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
<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)">
|
||||
<button v-if="termin.allowedToSave && !terminIsInvalid(termin)" 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>
|
||||
@@ -944,7 +945,8 @@ export const AbgabeMitarbeiterDetail = {
|
||||
<bs-modal
|
||||
ref="modalContainerZusatzdaten"
|
||||
class="bootstrap-prompt"
|
||||
dialogClass="bordered-modal modal-lg">
|
||||
dialogClass="bordered-modal modal-lg"
|
||||
bodyClass="px-4 py-4">
|
||||
<template v-slot:title>
|
||||
<div>
|
||||
{{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}}
|
||||
|
||||
@@ -3,6 +3,9 @@ import BsModal from '../../Bootstrap/Modal.js';
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
import ApiAbgabe from '../../../api/factory/abgabe.js'
|
||||
import FhcOverlay from "../../Overlay/FhcOverlay.js";
|
||||
import { formatISODate, getViennaTodayISO } from "./dateUtils.js";
|
||||
import { validateThesisTitle } from './titleValidation.js'
|
||||
|
||||
|
||||
export const AbgabeStudentDetail = {
|
||||
name: "AbgabeStudentDetail",
|
||||
@@ -20,7 +23,16 @@ export const AbgabeStudentDetail = {
|
||||
VueDatePicker,
|
||||
FhcOverlay
|
||||
},
|
||||
inject: ['notenOptions', 'isMobile', 'isViewMode', 'moodle_link'],
|
||||
inject: [
|
||||
'notenOptions',
|
||||
'isMobile',
|
||||
'isViewMode',
|
||||
'moodle_link',
|
||||
'confetti_on_endupload',
|
||||
'title_edit_allowed',
|
||||
'siginfolink_german',
|
||||
'siginfolink_english'
|
||||
],
|
||||
props: {
|
||||
projektarbeit: {
|
||||
type: Object,
|
||||
@@ -31,12 +43,14 @@ export const AbgabeStudentDetail = {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['titel-updated'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
eidAkzeptiert: false,
|
||||
enduploadTermin: null,
|
||||
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
|
||||
editingTitel: '',
|
||||
form: Vue.reactive({
|
||||
sprache: '',
|
||||
abstract: '',
|
||||
@@ -49,9 +63,120 @@ export const AbgabeStudentDetail = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confettiCannons() {
|
||||
const container = document.getElementById('confetti-container');
|
||||
if (!container) return;
|
||||
|
||||
const colors = ['#FFC107', '#FF5722', '#E91E63', '#00BCD4', '#4CAF50', '#9C27B0'];
|
||||
const shapes = ['50%', '0%'];
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
// Corner Cannons - Slowed Down)
|
||||
const cannonCount = 150;
|
||||
|
||||
for (let i = 0; i < cannonCount; i++) {
|
||||
const leftConfetti = document.createElement('div');
|
||||
leftConfetti.classList.add('confetti-piece');
|
||||
leftConfetti.style.left = '0px';
|
||||
leftConfetti.style.top = '100%';
|
||||
|
||||
const rightConfetti = document.createElement('div');
|
||||
rightConfetti.classList.add('confetti-piece');
|
||||
rightConfetti.style.left = '100vw';
|
||||
rightConfetti.style.top = '100%';
|
||||
|
||||
const colorL = colors[Math.floor(Math.random() * colors.length)];
|
||||
const colorR = colors[Math.floor(Math.random() * colors.length)];
|
||||
const shapeL = shapes[Math.floor(Math.random() * shapes.length)];
|
||||
const shapeR = shapes[Math.floor(Math.random() * shapes.length)];
|
||||
|
||||
// Left Styles
|
||||
leftConfetti.style.background = colorL;
|
||||
leftConfetti.style.borderRadius = shapeL;
|
||||
leftConfetti.style.width = `${Math.random() * 10 + 6}px`;
|
||||
leftConfetti.style.height = `${Math.random() * 14 + 6}px`;
|
||||
leftConfetti.style.setProperty('--blast-x', `${Math.random() * 50 + 10}vw`);
|
||||
leftConfetti.style.setProperty('--blast-y', `-${Math.random() * 65 + 30}vh`);
|
||||
|
||||
// Right Styles
|
||||
rightConfetti.style.background = colorR;
|
||||
rightConfetti.style.borderRadius = shapeR;
|
||||
rightConfetti.style.width = `${Math.random() * 10 + 6}px`;
|
||||
rightConfetti.style.height = `${Math.random() * 14 + 6}px`;
|
||||
rightConfetti.style.setProperty('--blast-x', `-${Math.random() * 50 + 10}vw`);
|
||||
rightConfetti.style.setProperty('--blast-y', `-${Math.random() * 65 + 30}vh`);
|
||||
|
||||
// Increased durations to 3s - 5s for a floating gravity effect
|
||||
const durationL = Math.random() * 2 + 3;
|
||||
const durationR = Math.random() * 2 + 3;
|
||||
const delayL = Math.random() * 0.2;
|
||||
const delayR = Math.random() * 0.2;
|
||||
|
||||
leftConfetti.style.animation = `cannonBlast ${durationL}s linear ${delayL}s both`;
|
||||
rightConfetti.style.animation = `cannonBlast ${durationR}s linear ${delayR}s both`;
|
||||
|
||||
fragment.appendChild(leftConfetti);
|
||||
fragment.appendChild(rightConfetti);
|
||||
|
||||
setTimeout(() => leftConfetti.remove(), (delayL + durationL) * 1000);
|
||||
setTimeout(() => rightConfetti.remove(), (delayR + durationR) * 1000);
|
||||
}
|
||||
|
||||
container.appendChild(fragment);
|
||||
},
|
||||
openTitelEdit() {
|
||||
this.editingTitel = this.projektarbeit.titel ?? '';
|
||||
this.$refs.modalTitelEdit.show();
|
||||
},
|
||||
async saveTitel() {
|
||||
|
||||
const validation = validateThesisTitle(this.editingTitel);
|
||||
|
||||
if (!validation.isValid) {
|
||||
if (validation.error === 'empty') {
|
||||
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4emptyThesisTitle'))
|
||||
} else if (validation.error === 'invalid_characters') {
|
||||
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4invalidCharactersThesisTitle'))
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const confirmed = await this.$fhcAlert.confirm({
|
||||
message: this.$p.t('abgabetool/c4confirmTitelSpeichern'),
|
||||
acceptLabel: this.$capitalize(this.$p.t('ui/speichern')),
|
||||
acceptClass: 'p-button-primary',
|
||||
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
|
||||
rejectClass: 'p-button-secondary'
|
||||
});
|
||||
|
||||
if (confirmed === false) return;
|
||||
|
||||
this.loading = true;
|
||||
this.$api.call(
|
||||
ApiAbgabe.postStudentProjektarbeitTitel(
|
||||
this.projektarbeit.projektarbeit_id,
|
||||
validation.cleanedTitle
|
||||
)
|
||||
).then(res => {
|
||||
if (res.meta.status === 'success') {
|
||||
this.projektarbeit.titel = res.data;
|
||||
this.$emit('titel-updated', {
|
||||
projektarbeit_id: this.projektarbeit.projektarbeit_id,
|
||||
titel: res.data
|
||||
});
|
||||
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess')));
|
||||
this.$refs.modalTitelEdit.hide();
|
||||
} else {
|
||||
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4titelSaveError')));
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getNoteBezeichnung(termin){
|
||||
const noteOpt = this.notenOptions.find(opt => opt.note == termin.note)
|
||||
|
||||
|
||||
if(noteOpt?.bezeichnung) {
|
||||
return noteOpt?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
|
||||
} else if(noteOpt?.benotbar === true && !termin.note) {
|
||||
@@ -65,7 +190,7 @@ export const AbgabeStudentDetail = {
|
||||
this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningChooseFile')));
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if(endupload) {
|
||||
if(await this.$fhcAlert.confirm({
|
||||
message: this.$p.t('abgabetool/confirmEnduploadSpeichern'),
|
||||
@@ -77,16 +202,16 @@ export const AbgabeStudentDetail = {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
async triggerEndupload() {
|
||||
|
||||
|
||||
if (!await this.validate(this.enduploadTermin, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// post endabgabe
|
||||
const formData = new FormData();
|
||||
formData.append('paabgabetyp_kurzbz', this.enduploadTermin.paabgabetyp_kurzbz)
|
||||
@@ -94,14 +219,14 @@ export const AbgabeStudentDetail = {
|
||||
formData.append('paabgabe_id', this.enduploadTermin.paabgabe_id)
|
||||
formData.append('student_uid', this.projektarbeit.student_uid)
|
||||
formData.append('bperson_id', this.projektarbeit.bperson_id)
|
||||
|
||||
|
||||
formData.append('sprache', this.form['sprache'].sprache)
|
||||
formData.append('abstract', this.form['abstract'])
|
||||
formData.append('abstract_en', this.form['abstract_en'])
|
||||
formData.append('schlagwoerter', this.form['schlagwoerter'])
|
||||
formData.append('schlagwoerter_en', this.form['schlagwoerter_en'])
|
||||
formData.append('seitenanzahl', this.form['seitenanzahl'])
|
||||
|
||||
|
||||
for (let i = 0; i < this.enduploadTermin.file.length; i++) {
|
||||
formData.append('file', this.enduploadTermin.file[i]);
|
||||
}
|
||||
@@ -109,39 +234,31 @@ export const AbgabeStudentDetail = {
|
||||
this.$api.call(ApiAbgabe.postStudentProjektarbeitEndupload(formData))
|
||||
.then(res => {
|
||||
this.handleUploadRes(res, this.enduploadTermin)
|
||||
if(this.confetti_on_endupload && res.meta.status == "success") {
|
||||
this.confettiCannons()
|
||||
}
|
||||
}).finally(()=> {
|
||||
this.loading = false
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
|
||||
this.$refs.modalContainerEnduploadZusatzdaten.hide()
|
||||
},
|
||||
downloadAbgabe(termin) {
|
||||
const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${termin.paabgabe_id}&student_uid=${this.projektarbeit.student_uid}&projektarbeit_id=${this.projektarbeit.projektarbeit_id}`;
|
||||
|
||||
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) {
|
||||
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}`
|
||||
return formatISODate(dateParam)
|
||||
},
|
||||
async upload(termin) {
|
||||
|
||||
// only do this on endupload
|
||||
if (! await this.validate(termin))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if(termin.bezeichnung?.paabgabetyp_kurzbz === 'end') {
|
||||
// open endupload form modal for further inputs
|
||||
this.enduploadTermin = termin
|
||||
this.$refs.modalContainerEnduploadZusatzdaten.show()
|
||||
} else {
|
||||
@@ -151,7 +268,7 @@ export const AbgabeStudentDetail = {
|
||||
formData.append('paabgabe_id', termin.paabgabe_id)
|
||||
formData.append('student_uid', this.projektarbeit.student_uid)
|
||||
formData.append('bperson_id', this.projektarbeit.bperson_id)
|
||||
|
||||
|
||||
for (let i = 0; i < termin.file.length; i++) {
|
||||
formData.append('file', termin.file[i]);
|
||||
}
|
||||
@@ -161,7 +278,7 @@ export const AbgabeStudentDetail = {
|
||||
.then(res => {
|
||||
this.handleUploadRes(res, termin)
|
||||
}).finally(()=> {
|
||||
this.loading = false
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -169,21 +286,18 @@ export const AbgabeStudentDetail = {
|
||||
if(res.meta.status == "success") {
|
||||
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];
|
||||
termin.abgabedatum = getViennaTodayISO();
|
||||
if(res?.data?.signatur !== undefined) {
|
||||
termin.signatur = res.data.signatur
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4fileUploadErrorv3')))
|
||||
}
|
||||
|
||||
|
||||
if(res.meta.signaturInfo) {
|
||||
this.$fhcAlert.alertInfo(res.meta.signaturInfo)
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
getOptionLabel(option) {
|
||||
return option.sprache
|
||||
@@ -195,7 +309,6 @@ export const AbgabeStudentDetail = {
|
||||
},
|
||||
watch: {
|
||||
projektarbeit(newVal) {
|
||||
// default select german if projektarbeit sprache was null
|
||||
this.form.sprache = newVal.sprache ? this.allActiveLanguages.find(lang => lang.sprache == newVal.sprache) : this.allActiveLanguages.find(lang => lang.sprache == 'German')
|
||||
this.form.abstract = newVal.abstract ?? ''
|
||||
this.form.abstract_en = newVal.abstract_en ?? ''
|
||||
@@ -203,15 +316,22 @@ export const AbgabeStudentDetail = {
|
||||
this.form.schlagwoerter_en = newVal.schlagwoerter_en ?? ''
|
||||
this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter ?? ''
|
||||
this.form.seitenanzahl = newVal.seitenanzahl ?? 1
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getSignaturInfoLink() {
|
||||
if(this.$p.user_language.value == 'German' && this.siginfolink_german) return this.siginfolink_german
|
||||
else if (this.$p.user_language.value == 'English' && this.siginfolink_english) return this.siginfolink_english
|
||||
},
|
||||
getSignaturInfoAvailable() {
|
||||
if(this.$p.user_language.value == 'German' && this.siginfolink_german) return true
|
||||
else if (this.$p.user_language.value == 'English' && this.siginfolink_english) return true
|
||||
else return false
|
||||
},
|
||||
getMoodleLink() {
|
||||
return this.moodle_link + this.projektarbeit.studiengang_kz
|
||||
return this.moodle_link + this.projektarbeit.studiengang_kz
|
||||
},
|
||||
getMessagePtStyle() {
|
||||
// adjust outer spacing and internal padding to appear similar to doenload button in size
|
||||
return {
|
||||
root: {
|
||||
style: {
|
||||
@@ -244,85 +364,46 @@ export const AbgabeStudentDetail = {
|
||||
})
|
||||
return qgatefound
|
||||
},
|
||||
isTitelEditAllowed() {
|
||||
return this.title_edit_allowed && !this.isViewMode && !this.projektarbeit?.note;
|
||||
},
|
||||
getTooltipVerspaetet() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerspaetet')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerspaetet')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipVerpasst() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerpasst')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerpasst')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipAbzugeben() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbzugeben')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbzugeben')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipStandard() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipStandardv2')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipStandardv2')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipAbgegeben() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgegeben')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgegeben')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipFixtermin() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipFixtermin')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipFixtermin')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipAbgabeDetected() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgabeDetected')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgabeDetected')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipNotAllowedToUpload() {
|
||||
if(this.isViewMode) {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedInViewMode')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
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"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedRegular')), class: "custom-tooltip" }
|
||||
}
|
||||
},
|
||||
getTooltipBeurteilungerforderlich() {
|
||||
return {
|
||||
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich')),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich')), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipBestanden() {
|
||||
return {
|
||||
value: this.$p.t('abgabetool/c4tooltipBestanden'),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$p.t('abgabetool/c4tooltipBestanden'), class: "custom-tooltip" }
|
||||
},
|
||||
getTooltipNichtBestanden() {
|
||||
return {
|
||||
value: this.$p.t('abgabetool/c4tooltipNichtBestanden'),
|
||||
class: "custom-tooltip"
|
||||
}
|
||||
return { value: this.$p.t('abgabetool/c4tooltipNichtBestanden'), class: "custom-tooltip" }
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
template: `
|
||||
<FhcOverlay :active="loading"></FhcOverlay>
|
||||
@@ -332,14 +413,35 @@ export const AbgabeStudentDetail = {
|
||||
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereich') )}}</h5>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<p> {{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}</p>
|
||||
<p> {{$capitalize( $p.t('abgabetool/c4titel') ) }}: {{projektarbeit?.titel}}</p>
|
||||
<p> {{$capitalize( $p.t('abgabetool/c4betreuerv2') ) }}: {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}</p>
|
||||
<p>{{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}</p>
|
||||
|
||||
<p class="d-flex align-items-center gap-2 mb-2" style="min-width: 0;">
|
||||
<span
|
||||
:title="projektarbeit.titel"
|
||||
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 480px;"
|
||||
>{{$capitalize( $p.t('abgabetool/c4titel') ) }}: {{projektarbeit?.titel}}</span>
|
||||
<button
|
||||
v-if="isTitelEditAllowed"
|
||||
class="btn btn-sm btn-outline-secondary border-0 p-1"
|
||||
v-tooltip.right="{ value: $capitalize($p.t('abgabetool/c4titelBearbeiten')), class: 'custom-tooltip' }"
|
||||
@click="openTitelEdit"
|
||||
>
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p>{{$capitalize( $p.t('abgabetool/c4betreuerv2') ) }}: {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}</p>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<p>{{ $p.t('abgabetool/c4checkoutStgMoodleInfos') }}
|
||||
<a :href="getMoodleLink" target="_blank">Moodle</a>
|
||||
</p>
|
||||
<div class="row">
|
||||
<p>{{ $p.t('abgabetool/c4checkoutStgMoodleInfos') }}
|
||||
<a :href="getMoodleLink" target="_blank">Moodle</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="getSignaturInfoAvailable">
|
||||
<a :href="getSignaturInfoLink" target="_blank">{{$p.t('abgabetool/c4signaturinfo')}} <i class="fa-solid fa-circle-info"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -357,7 +459,6 @@ export const AbgabeStudentDetail = {
|
||||
<i v-else-if="termin.dateStyle == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>
|
||||
<i v-else-if="termin.dateStyle == 'bestanden'" v-tooltip.right="getTooltipBestanden" class="fa-solid fa-check"></i>
|
||||
<i v-else-if="termin.dateStyle == 'nichtbestanden'" v-tooltip.right="getTooltipNichtBestanden" class="fa-solid fa-circle-exclamation"></i>
|
||||
|
||||
</div>
|
||||
<div class="text-start px-2" style="min-width: 150px; max-width: 300px; margin-left: 40px">
|
||||
<span>{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}</span>
|
||||
@@ -408,8 +509,6 @@ export const AbgabeStudentDetail = {
|
||||
</div>
|
||||
</template>
|
||||
</Inplace>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
@@ -430,7 +529,7 @@ export const AbgabeStudentDetail = {
|
||||
</VueDatePicker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}</div>
|
||||
<div class="col-12 col-md-9">
|
||||
@@ -466,7 +565,7 @@ export const AbgabeStudentDetail = {
|
||||
<div class="col-12 col-md-9">
|
||||
<template v-if="termin?.abgabedatum">
|
||||
<div class="row">
|
||||
<div style="width:100px; align-content: center;">
|
||||
<div style="width:100px; align-content: end;">
|
||||
<h6>{{ termin.abgabedatum?.split("-").reverse().join(".") }}</h6>
|
||||
</div>
|
||||
|
||||
@@ -481,9 +580,6 @@ export const AbgabeStudentDetail = {
|
||||
<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>-->
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -542,25 +638,66 @@ export const AbgabeStudentDetail = {
|
||||
<h5>{{ $capitalize( $p.t('abgabetool/c4keineAbgabetermineGefunden') )}}</h5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="confetti_on_endupload" id="confetti-container"></div>
|
||||
|
||||
<bs-modal
|
||||
ref="modalTitelEdit"
|
||||
class="bootstrap-prompt"
|
||||
dialogClass="bordered-modal"
|
||||
bodyClass="px-4 py-4"
|
||||
>
|
||||
<template v-slot:title>
|
||||
{{$capitalize( $p.t('abgabetool/c4titelBearbeiten') )}}
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<div class="mb-2">
|
||||
<label class="form-label fw-bold">
|
||||
{{$capitalize( $p.t('abgabetool/c4titel') )}}
|
||||
</label>
|
||||
<Textarea
|
||||
v-model="editingTitel"
|
||||
rows="10"
|
||||
maxlength="1024"
|
||||
class="form-control w-100"
|
||||
@keydown.enter.prevent="saveTitel"
|
||||
/>
|
||||
<div class="form-text text-end">{{ editingTitel.length }} / 1024</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="$refs.modalTitelEdit.hide()"
|
||||
>
|
||||
{{$capitalize( $p.t('abgabetool/c4Cancel') )}}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="!editingTitel.trim()"
|
||||
@click="saveTitel"
|
||||
>
|
||||
<i class="fa-solid fa-floppy-disk me-1"></i>
|
||||
{{$capitalize( $p.t('ui/speichern') )}}
|
||||
</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
|
||||
<bs-modal
|
||||
ref="modalContainerEnduploadZusatzdaten"
|
||||
class="bootstrap-prompt"
|
||||
dialogClass="bordered-modal modal-lg">
|
||||
dialogClass="bordered-modal modal-lg"
|
||||
bodyClass="px-4 py-4">
|
||||
<template v-slot:title>
|
||||
<div>
|
||||
{{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}}
|
||||
</div>
|
||||
<div class="row mb-3 align-items-start">
|
||||
|
||||
<p class="ml-4 mr-4">Student UID: {{ projektarbeit?.student_uid}}</p>
|
||||
|
||||
</div>
|
||||
<div class="row mb-3 align-items-start">
|
||||
|
||||
<p class="ml-4 mr-4">{{$capitalize( $p.t('abgabetool/c4titel') )}}: {{ projektarbeit?.titel }}</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
@@ -576,15 +713,6 @@ export const AbgabeStudentDetail = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- lektor fills these out-->
|
||||
<!-- <div class="row mb-3 align-items-start">-->
|
||||
<!-- <div class="row">Kontrollierte Schlagwörter</div>-->
|
||||
<!-- <div class="row">-->
|
||||
<!-- <Textarea v-model="form.kontrollschlagwoerter"></Textarea>-->
|
||||
<!-- </div>-->
|
||||
<!-- -->
|
||||
<!-- -->
|
||||
<!-- </div>-->
|
||||
<div class="row mb-3 align-items-start">
|
||||
<div class="row">{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}</div>
|
||||
<div class="row">
|
||||
@@ -631,7 +759,6 @@ export const AbgabeStudentDetail = {
|
||||
<div class="col-9"></div>
|
||||
<div class="col-2"><p>{{$capitalize( $p.t('abgabetool/c4gelesenUndAkzeptiert') )}}</p></div>
|
||||
<div class="col-1">
|
||||
|
||||
<Checkbox
|
||||
v-model="eidAkzeptiert"
|
||||
:binary="true"
|
||||
@@ -647,8 +774,8 @@ export const AbgabeStudentDetail = {
|
||||
<div v-show="!allowedToSaveZusatzdaten">{{ $p.t('abgabetool/c4zusatzdatenausfuellen') }}</div>
|
||||
<button class="btn btn-primary" :disabled="!getAllowedToSendEndupload" @click="triggerEndupload">{{$capitalize( $p.t('ui/hochladen') )}}</button>
|
||||
</template>
|
||||
|
||||
</bs-modal>
|
||||
|
||||
`,
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ import FhcOverlay from "../../Overlay/FhcOverlay.js";
|
||||
import { getDateStyleClass } from "./getDateStyleClass.js";
|
||||
import { dateFilter } from '../../../tabulator/filters/Dates.js';
|
||||
import {splitMailsHelper} from "../../../helpers/EmailHelpers.js";
|
||||
import { formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js";
|
||||
|
||||
export const AbgabetoolMitarbeiter = {
|
||||
name: "AbgabetoolMitarbeiter",
|
||||
@@ -33,7 +34,12 @@ export const AbgabetoolMitarbeiter = {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: null,
|
||||
filteredRows: null,
|
||||
count: 0,
|
||||
filteredcount: 0,
|
||||
selectedcount: 0,
|
||||
qgate1FilterSelected: [],
|
||||
qgate2FilterSelected: [],
|
||||
abgabetypenBetreuer: null,
|
||||
detailIsFullscreen: false,
|
||||
phrasenPromise: null,
|
||||
@@ -48,7 +54,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
allowedNotenOptions: null,
|
||||
notenOptionsNonFinal: null,
|
||||
serienTermin: Vue.reactive({
|
||||
datum: new Date(),
|
||||
datum: getViennaTodayISO(),
|
||||
bezeichnung: {
|
||||
paabgabetyp_kurzbz: 'zwischen',
|
||||
bezeichnung: 'Zwischenabgabe'
|
||||
@@ -70,7 +76,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
abgabeTableOptions: {
|
||||
minHeight: 250,
|
||||
index: 'projektarbeit_id',
|
||||
layout: 'fitDataStretch',
|
||||
layout: 'fitData',
|
||||
placeholder: Vue.computed(() => this.$p.t('global/noDataAvailable')),
|
||||
selectable: true,
|
||||
selectableCheck: this.selectionCheck,
|
||||
@@ -128,38 +134,65 @@ export const AbgabetoolMitarbeiter = {
|
||||
handleClick: this.selectAllHandler
|
||||
},
|
||||
width: 50,
|
||||
cssClass: 'sticky-col'
|
||||
cssClass: 'sticky-col',
|
||||
visible: true
|
||||
},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, headerFilter: false, headerSort: false, widthGrow: 1, tooltip: false, cssClass: 'sticky-col'},
|
||||
{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/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/c4betreuerartv2'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))), field: 'prevTermin',
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, headerFilter: false, headerSort: false, minWidth: 85, visible: true, tooltip: false, cssClass: 'sticky-col'},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, minWidth: 140, visible: false,tooltip: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100,visible: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100,visible: true},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, minWidth: 100,visible: true},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: true},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, visible: true, minWidth: 100},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, width: 500, visible: true},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerartv2'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, visible: true, minWidth: 100, width: 200},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))),
|
||||
headerFilter: dateFilter,
|
||||
headerFilterFunc: this.headerFilterTerminCol,
|
||||
sorter: this.sortFuncTerminCol,
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
|
||||
tooltip: this.toolTipFuncPrevTermin,
|
||||
field: 'prevTermin', formatter: this.abgabterminFormatter, width: 250, visible: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), field: 'nextTermin',
|
||||
headerFilter: dateFilter,
|
||||
headerFilterFunc: this.headerFilterTerminCol,
|
||||
sorter: this.sortFuncTerminCol,
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
|
||||
tooltip: this.toolTipFuncNextTermin,
|
||||
formatter: this.abgabterminFormatter, width: 250, visible: true},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: { valuesLookup: this.getQGateStatusList },
|
||||
field: 'qgate1Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
|
||||
headerFilter: this.qgateHeaderFilterEditor,
|
||||
headerFilterFunc: this.qgateHeaderFilterFunc,
|
||||
headerFilterParams: {},
|
||||
field: 'qgate1Status',
|
||||
formatter: this.centeredTextFormatter,
|
||||
titleFormatter: this.shortLongTitleFormatter,
|
||||
titleFormatterParams: {
|
||||
shortForm: 'QG1'
|
||||
},
|
||||
width: 50,
|
||||
tooltip: (e, cell) => {
|
||||
const data = cell.getData();
|
||||
return data.qgate1Status
|
||||
}
|
||||
},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate2Status'))),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: { valuesLookup: this.getQGateStatusList },
|
||||
field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false}
|
||||
headerFilter: this.qgateHeaderFilterEditor,
|
||||
headerFilterFunc: this.qgateHeaderFilterFunc,
|
||||
headerFilterParams: {},
|
||||
field: 'qgate2Status',
|
||||
formatter: this.centeredTextFormatter,
|
||||
titleFormatter: this.shortLongTitleFormatter,
|
||||
titleFormatterParams: {
|
||||
shortForm: 'QG2'
|
||||
},
|
||||
width: 50,
|
||||
tooltip: (e, cell) => {
|
||||
const data = cell.getData();
|
||||
return data.qgate2Status
|
||||
}
|
||||
}
|
||||
],
|
||||
persistence: false,
|
||||
persistenceID: 'abgabeTableBetreuer2026-02-26'
|
||||
persistenceID: 'abgabeTableBetreuer2026-05-26'
|
||||
},
|
||||
abgabeTableEventHandlers: [{
|
||||
event: "tableBuilt",
|
||||
@@ -190,11 +223,364 @@ export const AbgabetoolMitarbeiter = {
|
||||
})
|
||||
|
||||
this.selectedData = data
|
||||
this.selectedcount = data.length;
|
||||
}
|
||||
},
|
||||
{
|
||||
event: 'dataFiltered',
|
||||
handler: (filters, rows) => {
|
||||
this.filteredRows = rows;
|
||||
this.filteredcount = rows.length;
|
||||
|
||||
if (!this.selectedData.length) return;
|
||||
|
||||
const visibleData = new Set(rows.map(r => r.getData()));
|
||||
const filteredOut = this.selectedData.filter(sd => !visibleData.has(sd));
|
||||
|
||||
if (!filteredOut.length) return;
|
||||
|
||||
const filteredOutSet = new Set(filteredOut);
|
||||
this.$refs.abgabeTable.tabulator.getSelectedRows()
|
||||
.filter(r => filteredOutSet.has(r.getData()))
|
||||
.forEach(r => r.deselect());
|
||||
}
|
||||
}
|
||||
]};
|
||||
},
|
||||
methods: {
|
||||
async openBenotung(type, link) {
|
||||
if(type === 'new') {
|
||||
window.open(link, '_blank')
|
||||
} else if(type === 'old') {
|
||||
if(await this.$fhcAlert.confirm({
|
||||
message: this.$p.t('abgabetool/c4aeltereParbeitBenotenv2'),
|
||||
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
|
||||
acceptClass: 'btn btn-danger',
|
||||
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
|
||||
rejectClass: 'btn btn-outline-secondary'
|
||||
}) === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
window.open(link, '_blank')
|
||||
} else {
|
||||
// show info text that no endupload with abgabe has been found
|
||||
if(await this.$fhcAlert.confirm({
|
||||
message: this.$p.t('abgabetool/c4keinEnduploadErfolgt'),
|
||||
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
|
||||
acceptClass: 'btn btn-danger',
|
||||
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
|
||||
rejectClass: 'btn btn-outline-secondary'
|
||||
}) === false) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
formAction(cell) {
|
||||
const actionButtons = document.createElement('div');
|
||||
actionButtons.className = "d-flex gap-3";
|
||||
actionButtons.style.display = "flex";
|
||||
actionButtons.style.alignItems = "stretch";
|
||||
actionButtons.style.justifyContent = "start";
|
||||
actionButtons.style.height = "100%";
|
||||
|
||||
const val = cell.getValue();
|
||||
const data = cell.getRow().getData()
|
||||
|
||||
const createButton = (iconClass, titleKey, clickHandler) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-outline-secondary';
|
||||
btn.style.display = "flex";
|
||||
btn.style.alignItems = "center"; // center icon vertically
|
||||
btn.style.justifyContent = "center"; // center icon horizontally
|
||||
btn.style.height = "100%"; // fill parent container height
|
||||
btn.style.aspectRatio = "1 / 1"; // keep square shape (optional)
|
||||
btn.style.padding = "0"; // remove extra padding for compactness
|
||||
if(iconClass == 'fa fa-timeline') btn.style.transform = "rotate(90deg)";
|
||||
btn.innerHTML = `<i class="${iconClass}" style="color:#00649C; font-size:1.1rem;"></i>`;
|
||||
btn.title = this.$capitalize(this.$p.t(titleKey));
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
clickHandler();
|
||||
});
|
||||
return btn;
|
||||
};
|
||||
|
||||
actionButtons.append(
|
||||
createButton('fa fa-folder-open', 'abgabetool/c4details', () => this.setDetailComponent(val)),
|
||||
);
|
||||
|
||||
if(data.isCurrent && data.abgabetermine?.find(termin => termin.paabgabetyp_kurzbz == 'end' && termin.abgabedatum !== null) && data.beurteilungLinkNew) {
|
||||
actionButtons.append(createButton('fa fa-user-check', 'abgabetool/c4benoten', () => this.openBenotung('new', data.beurteilungLinkNew)))
|
||||
} else if(data.abgabetermine?.find(termin => termin.paabgabetyp_kurzbz == 'end' && termin.abgabedatum !== null) && data.beurteilungLinkOld) {
|
||||
actionButtons.append(createButton('fa fa-user-check', 'abgabetool/c4benoten', () => this.openBenotung('old', data.beurteilungLinkOld)))
|
||||
}
|
||||
|
||||
return actionButtons;
|
||||
},
|
||||
getDateStyleHtml(dateStyle) {
|
||||
const iconMap = {
|
||||
'verspaetet': '<i class="fa-solid fa-triangle-exclamation"></i>',
|
||||
'verpasst': '<i class="fa-solid fa-calendar-xmark"></i>',
|
||||
'abzugeben': '<i class="fa-solid fa-hourglass-half"></i>',
|
||||
'standard': '<i class="fa-solid fa-clock"></i>',
|
||||
'abgegeben': '<i class="fa-solid fa-paperclip"></i>',
|
||||
'beurteilungerforderlich': '<i class="fa-solid fa-list-check"></i>',
|
||||
'bestanden': '<i class="fa-solid fa-check"></i>',
|
||||
'nichtbestanden': '<i class="fa-solid fa-circle-exclamation"></i>',
|
||||
};
|
||||
return iconMap[dateStyle] ?? '';
|
||||
},
|
||||
statusHeaderFilterEditor(cell, onRendered, success, cancel, editorParams) {
|
||||
const options = [
|
||||
{ label: this.$p.t('abgabetool/c4positivBenotet'), value: 'bestanden', dateStyle: 'bestanden' },
|
||||
{ label: this.$p.t('abgabetool/c4negativBenotet'), value: 'nichtbestanden', dateStyle: 'nichtbestanden' },
|
||||
{ label: this.$p.t('abgabetool/c4tooltipVerspaetet'), value: 'verspaetet', dateStyle: 'verspaetet' },
|
||||
{ label: this.$p.t('abgabetool/c4tooltipVerpasst'), value: 'verpasst', dateStyle: 'verpasst' },
|
||||
{ label: this.$p.t('abgabetool/c4tooltipAbzugeben'), value: 'abzugeben', dateStyle: 'abzugeben' },
|
||||
{ label: this.$p.t('abgabetool/c4tooltipAbgegeben'), value: 'abgegeben', dateStyle: 'abgegeben' },
|
||||
{ label: this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich'), value: 'beurteilungerforderlich', dateStyle: 'beurteilungerforderlich' },
|
||||
{ label: this.$p.t('abgabetool/c4tooltipStandardv2'), value: 'standard', dateStyle: 'standard' },
|
||||
];
|
||||
|
||||
const field = cell.getField();
|
||||
const stateKey = field + 'FilterSelected'; // e.g. dateStyleFilterSelected
|
||||
let selected = [...(this[stateKey] || [])];
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.cssText = 'position: relative; width: 100%;';
|
||||
|
||||
const display = document.createElement('input');
|
||||
display.readOnly = true;
|
||||
display.placeholder = '';
|
||||
display.style.cssText = 'padding: 4px; width: 100%; box-sizing: border-box; cursor: default; border: 1px solid; outline: none; background: #fff; appearance: none; caret-color: transparent;';
|
||||
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.style.cssText = 'display: none; position: fixed; background: #fff; border: 1px solid; z-index: 9999; min-width: 220px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);';
|
||||
|
||||
const updateDisplay = () => {
|
||||
display.value = options
|
||||
.filter(o => selected.includes(o.value))
|
||||
.map(o => o.label)
|
||||
.join(', ');
|
||||
};
|
||||
|
||||
options.forEach(opt => {
|
||||
const row = document.createElement('label');
|
||||
row.style.cssText = 'display: flex; align-items: center; gap: 0; cursor: pointer; white-space: nowrap; padding-right: 8px;';
|
||||
row.addEventListener('mousedown', e => e.preventDefault());
|
||||
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.value = opt.value;
|
||||
cb.checked = selected.includes(opt.value);
|
||||
cb.style.cssText = 'margin: 0 6px;';
|
||||
cb.addEventListener('change', () => {
|
||||
selected = cb.checked
|
||||
? [...selected, opt.value]
|
||||
: selected.filter(v => v !== opt.value);
|
||||
this[stateKey] = [...selected];
|
||||
updateDisplay();
|
||||
success([...selected]);
|
||||
});
|
||||
|
||||
// icon badge — same look as cell
|
||||
const badge = document.createElement('div');
|
||||
badge.className = opt.dateStyle + '-header';
|
||||
badge.style.cssText = `min-width: 36px; height: 36px; display: flex; align-items: center;
|
||||
justify-content: center; flex-shrink: 0; padding: 0px 17px 0px 17px;`;
|
||||
badge.innerHTML = this.getDateStyleHtml(opt.dateStyle);
|
||||
|
||||
const labelText = document.createElement('span');
|
||||
labelText.textContent = opt.label;
|
||||
labelText.style.cssText = 'margin-left: 6px;';
|
||||
|
||||
row.appendChild(cb);
|
||||
row.appendChild(badge);
|
||||
row.appendChild(labelText);
|
||||
dropdown.appendChild(row);
|
||||
});
|
||||
|
||||
updateDisplay();
|
||||
|
||||
display.addEventListener('click', () => {
|
||||
if (dropdown.style.display === 'none') {
|
||||
const rect = display.getBoundingClientRect();
|
||||
dropdown.style.top = rect.bottom + 'px';
|
||||
dropdown.style.left = rect.left + 'px';
|
||||
dropdown.style.display = 'block';
|
||||
} else {
|
||||
dropdown.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
display.addEventListener('blur', () => {
|
||||
setTimeout(() => { dropdown.style.display = 'none'; }, 150);
|
||||
});
|
||||
|
||||
document.body.appendChild(dropdown);
|
||||
wrapper.appendChild(display);
|
||||
cell.getElement().addEventListener('remove', () => dropdown.remove());
|
||||
onRendered(() => display.focus());
|
||||
|
||||
return wrapper;
|
||||
},
|
||||
statusHeaderFilterFunc(filterVal, rowVal, rowData, filterParams) {
|
||||
if (!filterVal || !filterVal.length) return true;
|
||||
// rowVal is the raw dateStyle string on the flat table
|
||||
return filterVal.some(val => val === rowVal);
|
||||
},
|
||||
qgateHeaderFilterEditor(cell, onRendered, success, cancel, editorParams) {
|
||||
|
||||
const options = [
|
||||
{ label: '[+] ' + this.$p.t('abgabetool/c4positivBenotet'), value: 'positive' },
|
||||
{ label: '[-] ' + this.$p.t('abgabetool/c4negativBenotet'), value: 'negative' },
|
||||
{ label: '[~] ' + this.$p.t('abgabetool/c4notYetGraded'), value: 'not_graded' },
|
||||
{ label: '[?] ' + this.$p.t('abgabetool/c4notSubmitted'), value: 'not_submitted' },
|
||||
{ label: '[o] ' + this.$p.t('abgabetool/c4notHappenedYet'), value: 'not_happened' },
|
||||
{ label: '[--] ' + this.$p.t('abgabetool/c4keinTerminVorhanden'), value: 'no_termin' },
|
||||
];
|
||||
|
||||
const field = cell.getField();
|
||||
const stateKey = field === 'qgate1Status' ? 'qgate1FilterSelected' : 'qgate2FilterSelected';
|
||||
let selected = [...(this[stateKey] || [])]; // restore persistence state
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.cssText = 'position: relative; width: 100%;';
|
||||
|
||||
const display = document.createElement('input');
|
||||
display.readOnly = true;
|
||||
display.placeholder = '';
|
||||
display.style.cssText = 'padding: 4px; width: 100%; box-sizing: border-box; cursor: default; border: 1px solid; outline: none; background: #fff; appearance: none; caret-color: transparent;';
|
||||
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.style.cssText = 'display: none; position: fixed; background: #fff; border: 1px solid; z-index: 9999; min-width: 180px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);';
|
||||
|
||||
options.forEach(opt => {
|
||||
const row = document.createElement('label');
|
||||
row.style.cssText = 'display: flex; align-items: center; gap: 6px; padding: 4px 8px; cursor: pointer; white-space: nowrap;';
|
||||
row.addEventListener('mousedown', e => e.preventDefault());
|
||||
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.value = opt.value;
|
||||
cb.checked = selected.includes(opt.value); // sync with persistence
|
||||
cb.addEventListener('change', () => {
|
||||
if (cb.checked) {
|
||||
selected.push(opt.value);
|
||||
} else {
|
||||
selected = selected.filter(v => v !== opt.value);
|
||||
}
|
||||
this[stateKey] = [...selected]; // sync with persistence
|
||||
display.value = options.filter(o => selected.includes(o.value)).map(o => o.label).join(', ');
|
||||
success([...selected]);
|
||||
});
|
||||
|
||||
row.appendChild(cb);
|
||||
row.appendChild(document.createTextNode(opt.label));
|
||||
dropdown.appendChild(row);
|
||||
});
|
||||
|
||||
display.value = options.filter(o => selected.includes(o.value)).map(o => o.label).join(', ');
|
||||
|
||||
display.addEventListener('click', () => {
|
||||
if (dropdown.style.display === 'none') {
|
||||
const rect = display.getBoundingClientRect();
|
||||
dropdown.style.top = rect.bottom + 'px';
|
||||
dropdown.style.left = rect.left + 'px';
|
||||
dropdown.style.display = 'block';
|
||||
} else {
|
||||
dropdown.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
display.addEventListener('blur', () => {
|
||||
setTimeout(() => { dropdown.style.display = 'none'; }, 150);
|
||||
});
|
||||
|
||||
document.body.appendChild(dropdown);
|
||||
wrapper.appendChild(display);
|
||||
|
||||
cell.getElement().addEventListener('remove', () => dropdown.remove());
|
||||
|
||||
onRendered(() => display.focus());
|
||||
|
||||
return wrapper;
|
||||
},
|
||||
qgateHeaderFilterFunc(filterVal, rowVal, rowData, filterParams) {
|
||||
if (!filterVal || !filterVal.length) return true;
|
||||
|
||||
const matches = (val) => {
|
||||
switch (val) {
|
||||
case 'positive': return rowVal === this.$p.t('abgabetool/c4positivBenotet');
|
||||
case 'negative': return rowVal === this.$p.t('abgabetool/c4negativBenotet');
|
||||
case 'not_graded': return rowVal === this.$p.t('abgabetool/c4notYetGraded');
|
||||
case 'not_submitted':return rowVal === this.$p.t('abgabetool/c4notSubmitted');
|
||||
case 'not_happened': return rowVal === this.$p.t('abgabetool/c4notHappenedYet');
|
||||
case 'no_termin': return rowVal === this.$p.t('abgabetool/c4keinTerminVorhanden');
|
||||
default: return true;
|
||||
}
|
||||
};
|
||||
|
||||
// OR logic — row passes if it matches any selected filter
|
||||
return filterVal.some(val => matches(val));
|
||||
},
|
||||
shortLongTitleFormatter(cell, formatterParams, onRendered) {
|
||||
const longForm = cell.getValue()
|
||||
const shortForm = formatterParams?.shortForm
|
||||
|
||||
if(longForm && shortForm) {
|
||||
return `<span class="full-text" style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">
|
||||
${longForm}
|
||||
</span>
|
||||
<span class="short-text" style="font-weight: bold; display: none;">
|
||||
${shortForm}
|
||||
</span>`
|
||||
} else {
|
||||
return `<span class="full-text" style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">
|
||||
${longForm}
|
||||
</span>`
|
||||
}
|
||||
|
||||
},
|
||||
toolTipFuncPrevTermin(e, cell, onRendered) {
|
||||
const data = cell.getData();
|
||||
return this.mapDateStyleToTabulatorTooltip(data.prevTermin.dateStyle);
|
||||
},
|
||||
toolTipFuncNextTermin(e, cell, onRendered) {
|
||||
const data = cell.getData();
|
||||
return this.mapDateStyleToTabulatorTooltip(data.nextTermin.dateStyle);
|
||||
},
|
||||
mapDateStyleToTabulatorTooltip(dateStyleString) {
|
||||
switch(dateStyleString) {
|
||||
case 'bestanden':
|
||||
return this.$p.t('abgabetool/c4tooltipBestanden')
|
||||
break;
|
||||
case 'nichtbestanden':
|
||||
return this.$p.t('abgabetool/c4tooltipNichtBestanden')
|
||||
break;
|
||||
case 'beurteilungerforderlich':
|
||||
return this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich')
|
||||
break;
|
||||
case 'verspaetet':
|
||||
return this.$p.t('abgabetool/c4tooltipVerspaetet')
|
||||
break;
|
||||
case 'abgegeben':
|
||||
return this.$p.t('abgabetool/c4tooltipAbgegeben')
|
||||
break;
|
||||
case 'verpasst':
|
||||
return this.$p.t('abgabetool/c4tooltipVerpasst')
|
||||
break;
|
||||
case 'abzugeben':
|
||||
return this.$p.t('abgabetool/c4tooltipAbzugeben')
|
||||
break;
|
||||
case 'standard':
|
||||
return this.$p.t('abgabetool/c4tooltipStandardv2')
|
||||
break;
|
||||
default: return ''
|
||||
}
|
||||
},
|
||||
handlePaUpdated(projektarbeit) {
|
||||
this.checkAbgabetermineProjektarbeit(projektarbeit)
|
||||
this.$refs.abgabeTable.tabulator.redraw(true)
|
||||
@@ -207,7 +593,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
})
|
||||
const uniqueRecipients = [...new Set(recipientList)];
|
||||
const subject = ""; // empty subject line
|
||||
splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p)
|
||||
splitMailsHelper(uniqueRecipients, param.originalEvent, subject, null, this.$fhcAlert, this.$p)
|
||||
},
|
||||
getQGateStatusList() {
|
||||
return [
|
||||
@@ -247,7 +633,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
if (val instanceof Date) {
|
||||
dt = luxon.DateTime.fromJSDate(val);
|
||||
} else if (typeof val === "string") {
|
||||
dt = luxon.DateTime.fromISO(val);
|
||||
dt = toViennaDate(val);
|
||||
} else { // fallback
|
||||
dt = luxon.DateTime.fromMillis(Number(val));
|
||||
}
|
||||
@@ -376,6 +762,9 @@ export const AbgabetoolMitarbeiter = {
|
||||
}
|
||||
this.stateRestored = true
|
||||
|
||||
// ensure that the filterCollapseables thingy has the correct values
|
||||
this.$refs.abgabeTable.setSelectedFields();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -441,6 +830,32 @@ export const AbgabetoolMitarbeiter = {
|
||||
projekt.qgate2StatusRank = 1
|
||||
}
|
||||
})
|
||||
|
||||
// set shorthand statuscode once real status has been determined
|
||||
projekt.qgate1StatusShort = this.mapRankToShortStatus(projekt.qgate1StatusRank)
|
||||
projekt.qgate2StatusShort = this.mapRankToShortStatus(projekt.qgate2StatusRank)
|
||||
},
|
||||
mapRankToShortStatus(rank) {
|
||||
switch(rank){
|
||||
case 0: // kein termin vorhanden
|
||||
return '--'
|
||||
break;
|
||||
case 1: // noch nicht stattgefunden
|
||||
return 'o'
|
||||
break;
|
||||
case 2: // noch nicht abgegeben
|
||||
return '?'
|
||||
break;
|
||||
case 3: // noch nicht benotet
|
||||
return '~'
|
||||
break;
|
||||
case 4: // negativ benotet
|
||||
return '-'
|
||||
break;
|
||||
case 5: // positiv benotet
|
||||
return '+'
|
||||
break;
|
||||
}
|
||||
},
|
||||
checkAbgabetermineProjektarbeit(projekt) {
|
||||
const now = luxon.DateTime.now()
|
||||
@@ -450,7 +865,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
// while already looping through each termin, calculate datestyle beforehand
|
||||
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
|
||||
|
||||
const date = luxon.DateTime.fromISO(termin.datum).endOf('day')
|
||||
const date = toViennaDate(termin.datum).endOf('day')
|
||||
termin.luxonDate = date
|
||||
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
|
||||
|
||||
@@ -507,11 +922,11 @@ export const AbgabetoolMitarbeiter = {
|
||||
const bezeichnung = val.bezeichnung?.bezeichnung ?? val.bezeichnung
|
||||
|
||||
return '<div style="display: flex; height: 100%">' +
|
||||
'<div class=' + val.dateStyle + "-header" + ' style="width:48px; height: 100%; padding: 0px; display: flex; align-items: center; justify-content: center;">' +
|
||||
icon +
|
||||
'<div class=' + val.dateStyle + "-header" + ' style="min-width:48px; height: 100%; padding: 0px; display: flex; align-items: center; justify-content: center;">' +
|
||||
icon +
|
||||
'</div>' +
|
||||
'<div style="margin-left: 4px;">' +
|
||||
'<p style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+bezeichnung+' - '+ this.formatDate(val.datum)+'</p>' +
|
||||
'<p style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">'+bezeichnung+' - '+ this.formatDate(val.datum)+'</p>' +
|
||||
'</div>'+
|
||||
'</div>'
|
||||
|
||||
@@ -535,16 +950,19 @@ export const AbgabetoolMitarbeiter = {
|
||||
},
|
||||
selectAllHandler(e, cell) {
|
||||
const table = cell.getTable();
|
||||
const rows = table.getRows();
|
||||
const rows = this.filteredRows ?? table.getRows();
|
||||
|
||||
// custom select all logic
|
||||
const allowed = rows.filter(r => r.getData().selectable);
|
||||
// since betreuerpage acctually has logic behind selectable flag, it is important to go over allowed only here
|
||||
const selected = allowed.every(r => r.isSelected());
|
||||
|
||||
if(selected) {
|
||||
if(selected){
|
||||
allowed.forEach(r => r.deselect());
|
||||
e.target.checked = false;
|
||||
} else {
|
||||
allowed.forEach(r => r.select());
|
||||
e.target.checked = true;
|
||||
}
|
||||
|
||||
// stop built-in handler
|
||||
@@ -558,15 +976,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
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}`;
|
||||
return formatISODate(dateParam);
|
||||
},
|
||||
undoSelection(cell) {
|
||||
// checks if cells row is selected and unselects -> imitates columns which dont trigger row selection
|
||||
@@ -579,6 +989,8 @@ export const AbgabetoolMitarbeiter = {
|
||||
},
|
||||
selectionCheck(row) {
|
||||
const data = row.getData()
|
||||
|
||||
// zweitbetreuer cant select projektarbeiten for serientermine
|
||||
if(data?.betreuerart_kurzbz == 'Zweitbegutachter') return false
|
||||
return true
|
||||
},
|
||||
@@ -602,7 +1014,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
addSeries() {
|
||||
this.saving = true
|
||||
this.$api.call(ApiAbgabe.postSerientermin(
|
||||
this.serienTermin.datum.toISOString(),
|
||||
this.serienTermin.datum,
|
||||
this.serienTermin.bezeichnung.paabgabetyp_kurzbz,
|
||||
this.serienTermin.bezeichnung.bezeichnung,
|
||||
this.serienTermin.kurzbz,
|
||||
@@ -700,7 +1112,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
termin.allowedToSave = paIsBenotet ? false : true
|
||||
|
||||
// lektoren are not allowed to delete deadlines with existing submissions
|
||||
termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum
|
||||
termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum && !termin.note
|
||||
|
||||
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
|
||||
|
||||
@@ -717,28 +1129,37 @@ export const AbgabetoolMitarbeiter = {
|
||||
|
||||
},
|
||||
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>'
|
||||
const longForm = cell.getValue()
|
||||
if(!longForm) return
|
||||
const data = cell.getData()
|
||||
const entry = Object.entries(data).find(entry => entry[1] == longForm)
|
||||
|
||||
// shortFormKey must have same keyname as longForm but with 'Short' appended
|
||||
const shortForm = data[entry[0]+'Short']
|
||||
|
||||
if(shortForm && longForm) {
|
||||
return `<div style="display: flex; justify-content: start; align-items: center; height: 100%; width: 100%;">
|
||||
<span class="full-text" style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">
|
||||
${longForm}
|
||||
</span>
|
||||
<span class="short-text" style="font-weight: bold; display: none;">
|
||||
${shortForm}
|
||||
</span>
|
||||
</div>`;
|
||||
} else {
|
||||
return '<div style="display: flex; justify-content: start; align-items: center; height: 100%">' +
|
||||
'<p style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; margin: 0px;">'+longForm+'</p></div>'
|
||||
}
|
||||
},
|
||||
detailFormatter(cell) {
|
||||
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
|
||||
return '<div style="display: flex; justify-content: start; align-items: center; height: 100%">' +
|
||||
'<a><i class="fa fa-folder-open" 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%">' +
|
||||
'<a style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+val+'</a></div>'
|
||||
return '<div style="display: flex; justify-content: start; align-items: center; height: 100%">' +
|
||||
'<a style="max-width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">'+val+'</a></div>'
|
||||
},
|
||||
tableResolve(resolve) {
|
||||
this.tableBuiltResolve = resolve
|
||||
@@ -755,10 +1176,9 @@ export const AbgabetoolMitarbeiter = {
|
||||
setupData(data){
|
||||
|
||||
|
||||
this.projektarbeiten = data[0]
|
||||
this.domain = data[1]
|
||||
|
||||
this.tableData = data[0]?.retval?.map(projekt => {
|
||||
this.projektarbeiten = data[0]?.retval?.map(projekt => {
|
||||
this.checkAbgabetermineProjektarbeit(projekt)
|
||||
projekt.selectable = projekt.betreuerart_kurzbz !== 'Zweitbegutachter'
|
||||
|
||||
@@ -777,9 +1197,10 @@ export const AbgabetoolMitarbeiter = {
|
||||
titel: projekt.titel
|
||||
}
|
||||
})
|
||||
this.count = this.projektarbeiten.length
|
||||
|
||||
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
|
||||
this.$refs.abgabeTable.tabulator.setData(this.tableData);
|
||||
this.$refs.abgabeTable.tabulator.setData(this.projektarbeiten);
|
||||
},
|
||||
loadProjektarbeiten(all = false, callback) {
|
||||
this.$api.call(ApiAbgabe.getMitarbeiterProjektarbeiten(all))
|
||||
@@ -831,6 +1252,17 @@ export const AbgabetoolMitarbeiter = {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
countsToHTML() {
|
||||
return this.$p.t('global/ausgewaehlt')
|
||||
+ ': <strong>' + (this.selectedcount || 0) + '</strong>'
|
||||
+ ' | '
|
||||
+ this.$p.t('global/gefiltert')
|
||||
+ ': '
|
||||
+ '<strong>' + (this.filteredcount || 0) + '</strong>'
|
||||
+ ' | '
|
||||
+ this.$p.t('global/gesamt')
|
||||
+ ': <strong>' + (this.count || 0) + '</strong>';
|
||||
},
|
||||
emailItems() {
|
||||
const menu = []
|
||||
|
||||
@@ -859,6 +1291,8 @@ export const AbgabetoolMitarbeiter = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.documentElement.classList.add('abgabetool');
|
||||
|
||||
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
|
||||
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
|
||||
// fetch config to avoid hard coded links
|
||||
@@ -900,12 +1334,16 @@ export const AbgabetoolMitarbeiter = {
|
||||
mounted() {
|
||||
this.setupMounted()
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.documentElement.classList.remove('abgabetool');
|
||||
},
|
||||
template: `
|
||||
<template v-if="phrasenResolved">
|
||||
<FhcOverlay :active="loading || saving"></FhcOverlay>
|
||||
|
||||
<bs-modal ref="modalContainerAddSeries" class="bootstrap-prompt"
|
||||
dialogClass="modal-lg">
|
||||
dialogClass="modal-lg"
|
||||
bodyClass="px-4 py-4">
|
||||
<template v-slot:title>
|
||||
<div>
|
||||
{{ $p.t('abgabetool/neueTerminserie') }}
|
||||
@@ -925,6 +1363,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
:enable-time-picker="false"
|
||||
locale="de"
|
||||
format="dd.MM.yyyy"
|
||||
model-type="yyyy-MM-dd"
|
||||
:text-input="true"
|
||||
auto-apply>
|
||||
</VueDatePicker>
|
||||
@@ -972,7 +1411,8 @@ export const AbgabetoolMitarbeiter = {
|
||||
|
||||
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
|
||||
dialogClass="modal-xl" :allowFullscreenExpand="true"
|
||||
@toggle-fullscreen="handleToggleFullscreenDetail">
|
||||
@toggle-fullscreen="handleToggleFullscreenDetail"
|
||||
bodyClass="px-4 py-4">
|
||||
<template v-slot:title>
|
||||
<div>
|
||||
{{$p.t('abgabetool/c4abgabeMitarbeiterDetailTitle')}}
|
||||
@@ -991,12 +1431,13 @@ export const AbgabetoolMitarbeiter = {
|
||||
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
|
||||
<div id="abgabetable" style="max-height:40vw;">
|
||||
|
||||
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
|
||||
<h2>{{$p.t('abgabetool/abgabetoolTitleBetreuer')}}</h2>
|
||||
<hr>
|
||||
<core-filter-cmpt
|
||||
:title="''"
|
||||
@uuidDefined="handleUuidDefined"
|
||||
ref="abgabeTable"
|
||||
:description="countsToHTML"
|
||||
:newBtnShow="true"
|
||||
:newBtnLabel="$p.t('abgabetool/neueTerminserie')"
|
||||
:newBtnDisabled="!selectedData.length"
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import AbgabeDetail from "./AbgabeStudentDetail.js";
|
||||
import ApiAbgabe from '../../../api/factory/abgabe.js'
|
||||
import ApiAuthinfo from '../../../api/factory/authinfo.js';
|
||||
import BsModal from "../../Bootstrap/Modal.js";
|
||||
import FhcOverlay from "../../Overlay/FhcOverlay.js";
|
||||
import { getDateStyleClass} from "./getDateStyleClass.js";
|
||||
import ApiAuthinfo from "../../../api/factory/authinfo.js";
|
||||
import { validateThesisTitle } from './titleValidation.js'
|
||||
|
||||
export const AbgabetoolStudent = {
|
||||
name: "AbgabetoolStudent",
|
||||
components: {
|
||||
Accordion: primevue.accordion,
|
||||
AccordionTab: primevue.accordiontab,
|
||||
Textarea: primevue.textarea,
|
||||
BsModal,
|
||||
AbgabeDetail,
|
||||
FhcOverlay
|
||||
@@ -18,7 +21,11 @@ export const AbgabetoolStudent = {
|
||||
return {
|
||||
notenOptions: Vue.computed(() => this.notenOptions),
|
||||
isViewMode: Vue.computed(() => this.isViewMode),
|
||||
moodle_link: Vue.computed(() => this.moodle_link)
|
||||
moodle_link: Vue.computed(() => this.moodle_link),
|
||||
title_edit_allowed: Vue.computed(() => this.title_edit_allowed),
|
||||
confetti_on_endupload: Vue.computed(() => this.confetti_on_endupload),
|
||||
siginfolink_german: Vue.computed(() => this.siginfolink_german),
|
||||
siginfolink_english: Vue.computed(() => this.siginfolink_english)
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@@ -39,6 +46,13 @@ export const AbgabetoolStudent = {
|
||||
selectedProjektarbeit: null,
|
||||
moodle_link: null,
|
||||
uid: null,
|
||||
title_edit_allowed: null,
|
||||
confetti_on_endupload: null,
|
||||
siginfolink_german: null,
|
||||
siginfolink_english: null,
|
||||
editingTitel: '',
|
||||
editingProjektarbeit: null,
|
||||
uid: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -50,10 +64,67 @@ export const AbgabetoolStudent = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openTitelEdit(projektarbeit, event) {
|
||||
// stop the click from toggling the accordion tab
|
||||
event.stopPropagation();
|
||||
this.editingProjektarbeit = projektarbeit;
|
||||
this.editingTitel = projektarbeit.titel ?? '';
|
||||
this.$refs.modalTitelEdit.show();
|
||||
},
|
||||
async saveTitel() {
|
||||
const validation = validateThesisTitle(this.editingTitel);
|
||||
|
||||
if (!validation.isValid) {
|
||||
if (validation.error === 'empty') {
|
||||
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4emptyThesisTitle'))
|
||||
} else if (validation.error === 'invalid_characters') {
|
||||
this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4invalidCharactersThesisTitle'))
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const confirmed = await this.$fhcAlert.confirm({
|
||||
message: this.$p.t('abgabetool/c4confirmTitelSpeichern'),
|
||||
acceptLabel: this.$capitalize(this.$p.t('ui/speichern')),
|
||||
acceptClass: 'p-button-primary',
|
||||
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
|
||||
rejectClass: 'p-button-secondary'
|
||||
});
|
||||
|
||||
if (confirmed === false) return;
|
||||
|
||||
this.loading = true;
|
||||
this.$api.call(
|
||||
ApiAbgabe.postStudentProjektarbeitTitel(
|
||||
this.editingProjektarbeit.projektarbeit_id,
|
||||
validation.cleanedTitle
|
||||
)
|
||||
).then(res => {
|
||||
if (res.meta.status === 'success') {
|
||||
// update the local list entry in-place so the accordion header reflects it immediately
|
||||
this.editingProjektarbeit.titel = res.data;
|
||||
// keep the open detail modal in sync if it happens to be showing this projektarbeit
|
||||
if (this.selectedProjektarbeit?.projektarbeit_id === this.editingProjektarbeit.projektarbeit_id) {
|
||||
this.selectedProjektarbeit.titel = res.data;
|
||||
}
|
||||
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4titelSavedSuccess')));
|
||||
this.$refs.modalTitelEdit.hide();
|
||||
} else {
|
||||
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4titelSaveError')));
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleTitelUpdated(projektarbeit_id, titel) {
|
||||
const pa = this.projektarbeiten?.find(p => p.projektarbeit_id === projektarbeit_id);
|
||||
if (pa) pa.titel = titel;
|
||||
},
|
||||
checkQualityGatesStrict(termine) {
|
||||
let qgate1Passed = false
|
||||
let qgate2Passed = false
|
||||
|
||||
|
||||
termine.forEach(t => {
|
||||
const noteOption = this.notenOptions?.find(opt => opt.note == t.note)
|
||||
if(noteOption && noteOption.positiv) {
|
||||
@@ -70,7 +141,7 @@ export const AbgabetoolStudent = {
|
||||
checkQualityGatesOptional(termine) {
|
||||
const qgate1found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate1')
|
||||
const qgate2found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate2')
|
||||
|
||||
|
||||
let qgate1positiv = true
|
||||
if(qgate1found) {
|
||||
qgate1positiv = false
|
||||
@@ -111,47 +182,35 @@ export const AbgabetoolStudent = {
|
||||
this.loadAbgaben(details).then((res)=> {
|
||||
const pa = this.projektarbeiten?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
|
||||
pa.abgabetermine = res.data[0].retval
|
||||
|
||||
|
||||
const paIsBenotet = pa.note !== null
|
||||
|
||||
|
||||
pa.abgabetermine.forEach(termin => {
|
||||
termin.file = []
|
||||
termin.allowedToUpload = false
|
||||
|
||||
|
||||
if(termin.paabgabetyp_kurzbz == 'end') {
|
||||
// old assumed production logic when qgates are required
|
||||
// termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGatesStrict(pa.abgabetermine)
|
||||
|
||||
const inTime = termin.fixtermin ? !this.isPastDate(termin.datum) : true
|
||||
termin.allowedToUpload = inTime && this.checkQualityGatesOptional(pa.abgabetermine)
|
||||
|
||||
|
||||
// development purposes
|
||||
// termin.allowedToUpload = this.checkQualityGatesStrict(pa.abgabetermine)
|
||||
// termin.allowedToUpload = true
|
||||
|
||||
} else if(termin.fixtermin) {
|
||||
termin.allowedToUpload = !this.isPastDate(termin.datum)
|
||||
} else {
|
||||
// this could confuse people since we should dont show people this flag
|
||||
termin.allowedToUpload = termin.upload_allowed
|
||||
termin.allowedToUpload = termin.upload_allowed
|
||||
}
|
||||
|
||||
// blocks client upload button if projektarbeitet is already beurteilt und thus further abgaben on any termin should be blocked
|
||||
if(paIsBenotet) termin.allowedToUpload = false
|
||||
|
||||
|
||||
|
||||
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
|
||||
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
|
||||
})
|
||||
|
||||
|
||||
pa.betreuer = this.buildBetreuer(pa)
|
||||
pa.student_uid = this.student_uid
|
||||
|
||||
|
||||
this.selectedProjektarbeit = pa
|
||||
|
||||
this.$refs.modalContainerAbgabeDetail.show()
|
||||
|
||||
|
||||
}).finally(()=>{this.loading=false})
|
||||
},
|
||||
centeredTextFormatter(cell) {
|
||||
@@ -173,8 +232,8 @@ export const AbgabetoolStudent = {
|
||||
},
|
||||
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>'
|
||||
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()
|
||||
@@ -184,19 +243,17 @@ export const AbgabetoolStudent = {
|
||||
} else return '-'
|
||||
},
|
||||
buildMailToLink(projekt) {
|
||||
// should always be "projekt.mitarbeiter_uid +'@'+ this.domain", built in backend
|
||||
return 'mailto:' + projekt.email
|
||||
},
|
||||
buildBetreuer(abgabe) {
|
||||
return (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
|
||||
},
|
||||
async setupData(data){
|
||||
// this.projektarbeiten = data[0]
|
||||
const projektarbeiten = data[0] ?? null
|
||||
if(!projektarbeiten) return
|
||||
this.projektarbeiten = projektarbeiten.map(projekt => {
|
||||
let mode = 'detailTermine'
|
||||
|
||||
|
||||
return {
|
||||
...projekt,
|
||||
details: {
|
||||
@@ -230,16 +287,14 @@ export const AbgabetoolStudent = {
|
||||
.then(res => {
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
async setupMounted() {
|
||||
this.loadProjektarbeiten()
|
||||
},
|
||||
getAccTabHeaderForProjektarbeit(projektarbeit) {
|
||||
let title = ''
|
||||
|
||||
title += projektarbeit.titel ?? this.$p.t('abgabetool/keinTitel')
|
||||
|
||||
return title
|
||||
},
|
||||
getMailLink(projektarbeit) {
|
||||
@@ -266,12 +321,17 @@ export const AbgabetoolStudent = {
|
||||
this.uid = authIdResponse.data.uid;
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
async created() {
|
||||
// make sure zoom media query doesnt spill ever to other CIS4 sites
|
||||
document.documentElement.classList.add('abgabetool');
|
||||
|
||||
this.$api.call(ApiAuthinfo.getAuthUID()).then(res => this.uid = res.data.uid)
|
||||
|
||||
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 => {
|
||||
if(res.meta.status == 'success') {
|
||||
this.notenOptions = res.data[0]
|
||||
@@ -284,16 +344,18 @@ export const AbgabetoolStudent = {
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
// fetch abgabetypen options
|
||||
this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => {
|
||||
this.abgabeTypeOptions = res.data
|
||||
}).catch(e => {
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
// fetch config to avoid hard coded links
|
||||
this.$api.call(ApiAbgabe.getConfigStudent()).then(res => {
|
||||
this.moodle_link = res.data?.moodle_link
|
||||
this.title_edit_allowed = res.data?.title_edit_allowed
|
||||
this.confetti_on_endupload = res.data?.confetti_on_endupload
|
||||
this.siginfolink_german = res.data?.siginfolink_german
|
||||
this.siginfolink_english = res.data?.siginfolink_english
|
||||
}).catch(e => {
|
||||
this.loading = false
|
||||
})
|
||||
@@ -303,26 +365,73 @@ export const AbgabetoolStudent = {
|
||||
mounted() {
|
||||
this.setupMounted()
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.documentElement.classList.remove('abgabetool');
|
||||
},
|
||||
template: `
|
||||
<template v-if="phrasenResolved">
|
||||
<FhcOverlay :active="loading"></FhcOverlay>
|
||||
|
||||
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
|
||||
dialogClass="modal-xl" :allowFullscreenExpand="true">
|
||||
dialogClass="modal-xl" :allowFullscreenExpand="true" bodyClass="px-4 py-4">
|
||||
<template v-slot:title>
|
||||
<div>
|
||||
{{$capitalize( $p.t('abgabetool/c4abgabeStudentDetailTitle') )}}
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
|
||||
<AbgabeDetail
|
||||
:projektarbeit="selectedProjektarbeit"
|
||||
@titel-updated="handleTitelUpdated"
|
||||
></AbgabeDetail>
|
||||
</template>
|
||||
</bs-modal>
|
||||
<bs-modal
|
||||
ref="modalTitelEdit"
|
||||
class="bootstrap-prompt"
|
||||
dialogClass="bordered-modal"
|
||||
bodyClass="px-4 py-4"
|
||||
>
|
||||
<template v-slot:title>
|
||||
{{$capitalize( $p.t('abgabetool/c4titelBearbeiten') )}}
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<div class="mb-2">
|
||||
<label class="form-label fw-bold">
|
||||
{{$capitalize( $p.t('abgabetool/c4titel') )}}
|
||||
</label>
|
||||
<Textarea
|
||||
v-model="editingTitel"
|
||||
rows="10"
|
||||
maxlength="1024"
|
||||
class="form-control w-100"
|
||||
@keydown.enter.prevent="saveTitel"
|
||||
/>
|
||||
<div class="form-text text-end">{{ editingTitel.length }} / 1024</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="$refs.modalTitelEdit.hide()"
|
||||
>
|
||||
{{$capitalize( $p.t('abgabetool/c4Cancel') )}}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="!editingTitel.trim()"
|
||||
@click="saveTitel"
|
||||
>
|
||||
<i class="fa-solid fa-floppy-disk me-1"></i>
|
||||
{{$capitalize( $p.t('ui/speichern') )}}
|
||||
</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
|
||||
<h2>{{$capitalize( $p.t('abgabetool/abgabetoolTitle') )}}</h2>
|
||||
<hr>
|
||||
|
||||
<div v-if="projektarbeiten === null">
|
||||
<div v-if="projektarbeiten === null || projektarbeiten?.length == 0">
|
||||
{{$capitalize( $p.t('abgabetool/c4abgabeStudentNoProjectsFound') )}}
|
||||
</div>
|
||||
|
||||
@@ -332,8 +441,12 @@ export const AbgabetoolStudent = {
|
||||
|
||||
<template #header>
|
||||
<div class="d-flex row w-100">
|
||||
<div class="text-start" :class="projektarbeit.note != null ? 'col-6' : 'col-12'">
|
||||
<span>{{getAccTabHeaderForProjektarbeit(projektarbeit)}}</span>
|
||||
<div class="text-start" :class="projektarbeit.note != null ? 'col-6' : 'col-12'"
|
||||
style="min-width: 0;">
|
||||
<span
|
||||
:title="getAccTabHeaderForProjektarbeit(projektarbeit)"
|
||||
style="display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 600px;"
|
||||
>{{getAccTabHeaderForProjektarbeit(projektarbeit)}}</span>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
<span>{{getNoteBezeichnung(projektarbeit)}}</span>
|
||||
@@ -399,12 +512,33 @@ export const AbgabetoolStudent = {
|
||||
{{ projektarbeit.projekttypbezeichnung }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<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 class="col-8 col-md-9 d-flex align-items-center gap-2" style="min-width: 0;">
|
||||
<span
|
||||
:title="projektarbeit.titel"
|
||||
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
||||
>{{ projektarbeit.titel }}</span>
|
||||
<button
|
||||
v-if="title_edit_allowed && !isViewMode && projektarbeit.note == null"
|
||||
class="btn btn-sm btn-outline-secondary border-0 p-1"
|
||||
v-tooltip.right="{ value: $capitalize($p.t('abgabetool/c4titelBearbeiten')), class: 'custom-tooltip' }"
|
||||
@click="openTitelEdit(projektarbeit, $event)"
|
||||
>
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4note') )}}</div>
|
||||
|
||||
<div class="col-8 col-md-9">
|
||||
<span>{{getNoteBezeichnung(projektarbeit)}}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</AccordionTab>
|
||||
</template>
|
||||
</Accordion>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const zone = 'Europe/Vienna';
|
||||
|
||||
export function getViennaTodayISO() {
|
||||
return luxon.DateTime.now().setZone(zone).toISODate();
|
||||
}
|
||||
|
||||
export function formatISODate(dateParam) {
|
||||
if (!dateParam) return '';
|
||||
|
||||
const date = luxon.DateTime.fromISO(String(dateParam), { zone });
|
||||
return date.isValid ? date.toFormat('dd.MM.yyyy') : '';
|
||||
}
|
||||
|
||||
export function toViennaDate(dateParam) {
|
||||
if (!dateParam) return null;
|
||||
|
||||
return luxon.DateTime.fromISO(String(dateParam), { zone });
|
||||
}
|
||||
|
||||
export function compareISODateValues(a, b) {
|
||||
if (!a && !b) return 0;
|
||||
if (!a) return 1;
|
||||
if (!b) return -1;
|
||||
|
||||
return String(a).localeCompare(String(b));
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
const zone = 'Europe/Vienna';
|
||||
const today = luxon.DateTime.now().setZone(zone);
|
||||
|
||||
export function getDateStyleClass(termin, notenOptions) {
|
||||
const today = luxon.DateTime.now().setZone(zone);
|
||||
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
|
||||
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
|
||||
termin.diffindays = datum.diff(today, 'days').days;
|
||||
@@ -28,10 +28,11 @@ export function getDateStyleClass(termin, notenOptions) {
|
||||
|
||||
// no submission yet
|
||||
if (datum < today) return 'verpasst';
|
||||
if (termin.diffindays <= 12) return 'abzugeben';
|
||||
return 'standard';
|
||||
|
||||
}
|
||||
|
||||
// GENERIC STATUS
|
||||
return datum < today ? 'verpasst' : 'standard';
|
||||
}
|
||||
// GENERIC STATUS — applies to all termine
|
||||
if (datum < today) return 'verpasst';
|
||||
if (termin.diffindays <= 12) return 'abzugeben';
|
||||
return 'standard';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Validates the thesis title on the frontend
|
||||
* @param {string} title - The raw input from the title field
|
||||
* @returns {object} Validation result containing status and cleaned title
|
||||
*/
|
||||
export function validateThesisTitle(title) {
|
||||
if (!title) {
|
||||
return { isValid: false, error: 'empty' };
|
||||
}
|
||||
|
||||
// Replicate strip_tags / trim
|
||||
const cleanedTitle = title.replace(/<\/?[^>]+(>|$)/g, "").trim();
|
||||
|
||||
if (cleanedTitle === '') {
|
||||
return { isValid: false, error: 'empty' };
|
||||
}
|
||||
|
||||
// Replicate the emoji/pictograph rejection
|
||||
const emojiRegex = /\p{Extended_Pictographic}/u;
|
||||
if (emojiRegex.test(cleanedTitle)) {
|
||||
return { isValid: false, error: 'invalid_characters' };
|
||||
}
|
||||
|
||||
return { isValid: true, cleanedTitle: cleanedTitle };
|
||||
}
|
||||
@@ -319,6 +319,10 @@ export default {
|
||||
this.loadListGroup();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.formData.stgkz) {
|
||||
this.loadLvPlan();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="cis-lvplan-stg-org d-flex flex-column h-100">
|
||||
|
||||
@@ -338,8 +338,8 @@ export default {
|
||||
<div class="container-fluid text-break fhc-form" >
|
||||
<edit-profil v-if="showModal" ref="editModal" :isMitarbeiter="true" @hideBsModal="hideEditProfilModal" :value="JSON.parse(JSON.stringify(filteredEditData))" :titel="$p.t('profil','profilBearbeiten')"></edit-profil>
|
||||
<div class="row">
|
||||
<div class="d-md-none col-12 ">
|
||||
<!-- Bearbeiten Button -->
|
||||
<div class="d-md-none col-12">
|
||||
<!-- Bearbeiten Button -->
|
||||
<div v-if="isEditable" class="row mb-3 ">
|
||||
<div class="col">
|
||||
<button @click="()=>showEditProfilModal()" type="button" class="text-start card w-100 btn btn-outline-secondary" >
|
||||
@@ -352,9 +352,9 @@ export default {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MOBILE PROFIL UPDATES -->
|
||||
<div v-if="data.profilUpdates" class="row mb-3">
|
||||
<div class="col">
|
||||
<!-- MOBILE PROFIL UPDATES -->
|
||||
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates" ></fetch-profil-updates>
|
||||
</div>
|
||||
</div>
|
||||
@@ -367,30 +367,42 @@ export default {
|
||||
<!-- ROW WITH THE PROFIL INFORMATION -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-lg-12 col-xl-6 ">
|
||||
<!-- PROFIL INFORMATION -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- PROFIL INFORMATION -->
|
||||
<profil-information @showEditProfilModal="showEditProfilModal" :title="$p.t('profil','mitarbeiterIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
|
||||
</div>
|
||||
</div>
|
||||
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
|
||||
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- CALENDAR SYNC OPTIONS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
|
||||
<div class="row mb-4 d-md-none">
|
||||
<div class="col">
|
||||
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MITARBEITER INFO -->
|
||||
<div class="row mb-4">
|
||||
<div class=" col-lg-12">
|
||||
<!-- MITARBEITER INFO -->
|
||||
<role-information :title="$p.t('profil','mitarbeiterInformation')" :data="roleInformation"></role-information>
|
||||
</div>
|
||||
</div>
|
||||
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
|
||||
</div>
|
||||
<div class="col-xl-6 col-lg-12 ">
|
||||
<!-- EMAILS -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- EMAILS -->
|
||||
<profil-emails :title="this.$p.t('person','email')" :data="data.emails" ></profil-emails>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PRIVATE KONTAKTE-->
|
||||
<div class="row mb-4 ">
|
||||
<div class="col">
|
||||
<!-- PRIVATE KONTAKTE-->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
@@ -412,9 +424,9 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PRIVATE ADRESSEN-->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- PRIVATE ADRESSEN-->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
@@ -439,8 +451,8 @@ export default {
|
||||
</div>
|
||||
</div >
|
||||
<div class="row">
|
||||
<!-- FUNKTIONEN TABELLE -->
|
||||
<div class="col-12 mb-4" >
|
||||
<!-- FUNKTIONEN TABELLE -->
|
||||
<core-filter-cmpt
|
||||
v-if="arePhrasesPreloaded"
|
||||
@tableBuilt="funktionenTableBuilt"
|
||||
@@ -451,8 +463,8 @@ export default {
|
||||
:sideMenu="false"
|
||||
/>
|
||||
</div>
|
||||
<!-- BETRIEBSMITTEL TABELLE -->
|
||||
<div class="col-12 mb-4" >
|
||||
<!-- BETRIEBSMITTEL TABELLE -->
|
||||
<core-filter-cmpt
|
||||
v-if="arePhrasesPreloaded"
|
||||
@tableBuilt="betriebsmittelTableBuilt"
|
||||
@@ -467,11 +479,6 @@ export default {
|
||||
</div>
|
||||
<!-- START OF SIDE PANEL -->
|
||||
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
|
||||
<div v-if="quickLinks.length" class="row mb-4">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bearbeiten Button -->
|
||||
<div class="row d-none d-md-block ">
|
||||
<div class="col mb-3">
|
||||
@@ -485,29 +492,36 @@ export default {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
|
||||
<div class="col mb-3">
|
||||
<!-- PROFIL UPDATES -->
|
||||
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3" >
|
||||
<div class="col-12">
|
||||
<!-- AUSWEIS STATUS -->
|
||||
<ausweis-status :data="data.zutrittsdatum"></ausweis-status>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<!-- MAILVERTEILER -->
|
||||
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
|
||||
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- CALENDAR SYNC OPTIONS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
|
||||
<div class="row mb-3 d-none d-md-block">
|
||||
<div class="col">
|
||||
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PROFIL UPDATES -->
|
||||
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
|
||||
<div class="col mb-3">
|
||||
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
|
||||
</div>
|
||||
</div>
|
||||
<!-- AUSWEIS STATUS -->
|
||||
<div class="row mb-3" >
|
||||
<div class="col-12">
|
||||
<ausweis-status :data="data.zutrittsdatum"></ausweis-status>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MAILVERTEILER -->
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -225,15 +225,6 @@ export default {
|
||||
<div class="container-fluid text-break fhc-form" >
|
||||
<!-- ROW -->
|
||||
<div class="row">
|
||||
<!-- HIDDEN QUICK LINKS -->
|
||||
<!-- TODO: uncomment when implemented
|
||||
<div class="d-md-none col-12 ">
|
||||
|
||||
<quick-links :title="$p.t('profil','quickLinks')" :mobile="true" ></quick-links>
|
||||
|
||||
</div>
|
||||
-->
|
||||
<!-- END OF HIDDEN QUCK LINKS -->
|
||||
<!-- MAIN PANNEL -->
|
||||
<div class="col-sm-12 col-md-8 col-xxl-9 ">
|
||||
<!-- ROW WITH PROFIL IMAGE AND INFORMATION -->
|
||||
@@ -242,27 +233,33 @@ export default {
|
||||
<div class="row mb-4">
|
||||
<!-- FIRST KAESTCHEN -->
|
||||
<div class="col-lg-12 col-xl-6 ">
|
||||
<!-- Profil Informationen -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- Profil Informationen -->
|
||||
<profil-information :title="$p.t('profil','mitarbeiterIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
|
||||
</div>
|
||||
</div>
|
||||
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
|
||||
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
|
||||
<!-- END OF PROFIL INFORMATION ROW -->
|
||||
<!-- INFORMATION CONTENT END -->
|
||||
</div>
|
||||
<div class="col-xl-6 col-lg-12 ">
|
||||
<!-- EMAILS -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- EMAILS -->
|
||||
<profil-emails :title="this.$p.t('person','email')" :data="personEmails"></profil-emails>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SECOND ROW OF SECOND COLUMN IN MAIN CONTENT -->
|
||||
<!-- roleInformation -->
|
||||
<div class="row mb-4">
|
||||
<div class=" col-lg-12">
|
||||
<!-- roleInformation -->
|
||||
<role-information :data="roleInformation" :title="$p.t('profil','mitarbeiterInformation')"></role-information>
|
||||
</div>
|
||||
</div>
|
||||
@@ -284,15 +281,15 @@ export default {
|
||||
</div>
|
||||
<!-- START OF SIDE PANEL -->
|
||||
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
|
||||
<!-- VISIBLE UNTIL VIEWPORT MD -->
|
||||
<div v-if="quickLinks.length" class="row mb-4">
|
||||
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
|
||||
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- MAILVERTEILER -->
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<!-- MAILVERTEILER -->
|
||||
<mailverteiler :data="data?.mailverteiler" :title="$p.t('profil','mailverteiler')"></mailverteiler>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,6 +176,7 @@ export const Profil = {
|
||||
this.data = data.profil_data.data;
|
||||
this.calendarSyncUrls = data.calendar_sync_urls ?? [];
|
||||
this.authPermissions = data.permissions;
|
||||
console.log(data.profil_data);
|
||||
},
|
||||
zustellAdressenCount() {
|
||||
if (!this.data || !this.data.adressen) {
|
||||
|
||||
@@ -292,14 +292,13 @@ export default {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MOBILE PROFIL UPDATES -->
|
||||
<div v-if="data.profilUpdates" class="row mb-3">
|
||||
<div class="col">
|
||||
<!-- MOBILE PROFIL UPDATES -->
|
||||
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END OF HIDDEN QUCK LINKS -->
|
||||
|
||||
<!-- MAIN PANNEL -->
|
||||
<div class="col-sm-12 col-md-8 col-xxl-9 ">
|
||||
@@ -308,30 +307,42 @@ export default {
|
||||
<!-- ROW WITH THE PROFIL INFORMATION -->
|
||||
<div class="row mb-4 ">
|
||||
<div class="col-lg-12 col-xl-6 ">
|
||||
<!-- PROFIL INFORMATION -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- PROFIL INFORMATION -->
|
||||
<profil-information @showEditProfilModal="showEditProfilModal" :title="$p.t('profil','studentIn')" :data="profilInformation" :fotoStatus="fotoStatus"></profil-information>
|
||||
</div>
|
||||
</div>
|
||||
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
|
||||
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- CALENDAR SYNC OPTIONS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
|
||||
<div class="row mb-4 d-md-none">
|
||||
<div class="col">
|
||||
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
|
||||
</div>
|
||||
</div>
|
||||
<!-- STUDENT INFO -->
|
||||
<div class="row mb-4">
|
||||
<div class=" col-lg-12">
|
||||
<!-- STUDENT INFO -->
|
||||
<role-information :title="$p.t('profil','studentInformation')" :data="roleInformation"></role-information>
|
||||
</div>
|
||||
</div>
|
||||
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
|
||||
</div>
|
||||
<div class="col-xl-6 col-lg-12 ">
|
||||
<!-- EMAILS -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- EMAILS -->
|
||||
<profil-emails :title="this.$p.t('person','email')" :data="data.emails" ></profil-emails>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PRIVATE KONTAKTE-->
|
||||
<div class="row mb-4 ">
|
||||
<div class="col">
|
||||
<!-- PRIVATE KONTAKTE-->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
@@ -354,9 +365,9 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PRIVATE ADRESSEN-->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<!-- PRIVATE ADRESSEN-->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
@@ -408,11 +419,6 @@ export default {
|
||||
</div>
|
||||
<!-- START OF SIDE PANEL -->
|
||||
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
|
||||
<div v-if="quickLinks.length" class="row mb-4">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bearbeiten Button -->
|
||||
<div class="row d-none d-md-block">
|
||||
<div class="col mb-3">
|
||||
@@ -426,9 +432,21 @@ export default {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
|
||||
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- CALENDAR SYNC OPTIONS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
|
||||
<div class="row mb-3 d-none d-md-block">
|
||||
<div class="col">
|
||||
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PROFIL UPDATES -->
|
||||
<div v-if="data.profilUpdates" class="row d-none d-md-block mb-3">
|
||||
<div class="col mb-3">
|
||||
<!-- PROFIL UPDATES -->
|
||||
<fetch-profil-updates v-if="data.profilUpdates && data.profilUpdates.length" @fetchUpdates="fetchProfilUpdates" :data="data.profilUpdates"></fetch-profil-updates>
|
||||
</div>
|
||||
</div>
|
||||
@@ -438,18 +456,13 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
<!-- START OF THE SECOND ROW IN THE SIDE PANEL -->
|
||||
<!-- MAILVERTEILER -->
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<!-- HIER SIND DIE MAILVERTEILER -->
|
||||
<mailverteiler :title="$p.t('profil','mailverteiler')" :data="data?.mailverteiler"></mailverteiler>
|
||||
</div>
|
||||
<!-- END OF THE SECOND ROW IN THE SIDE PANEL -->
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<calendar-sync :uid="$props.data.username" :calendarSyncUrls="$props.calendarSyncUrls"></calendar-sync>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END OF THE SECOND ROW IN THE SIDE PANEL -->
|
||||
<!-- END OF SIDE PANEL -->
|
||||
</div>
|
||||
<!-- END OF CONTAINER ROW-->
|
||||
|
||||
@@ -121,12 +121,12 @@ export default {
|
||||
<profil-information :data="profilInformation" :title="$p.t('profil','studentIn')" :fotoStatus="fotoStatus"></profil-information>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SECOND ROW OF FIRST COLUMN -->
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- QUICK LINKS, MOBILE VIEW (HIDDEN IF VIEWPORT >= MD BREAKPOINT) -->
|
||||
<div v-if="quickLinks.length" class="row mb-4 d-md-none">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- START OF SECOND PROFIL INFORMATION COLUMN -->
|
||||
<!-- END OF PROFIL INFORMATION ROW -->
|
||||
<!-- INFORMATION CONTENT END -->
|
||||
@@ -155,7 +155,8 @@ export default {
|
||||
<!-- START OF SIDE PANEL -->
|
||||
<div class="col-md-4 col-xxl-3 col-sm-12 text-break" >
|
||||
<!-- START OF THE FIRST ROW IN THE SIDE PANEL -->
|
||||
<div v-if="quickLinks.length" class="row mb-4">
|
||||
<!-- QUICK LINKS, HIDDEN IF VIEWPORT < MD BREAKPOINT -->
|
||||
<div v-if="quickLinks.length" class="row mb-3 d-none d-md-block">
|
||||
<div class="col">
|
||||
<quick-links :title="$p.t('profil/quickLinks')" :links="quickLinks" />
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,11 @@ export default {
|
||||
event: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
timeSlotDisplayBehavior: {
|
||||
type: String,
|
||||
default: "default",
|
||||
// options: default, always, never
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
@@ -50,7 +55,17 @@ export default {
|
||||
return luxon.Duration
|
||||
.fromISOTime(this.event.ende)
|
||||
.toISOTime({ suppressSeconds: true });
|
||||
}
|
||||
},
|
||||
timeSlotDisplayClasses() {
|
||||
switch (this.$props.timeSlotDisplayBehavior) {
|
||||
case "always":
|
||||
return "d-grid";
|
||||
case "never":
|
||||
return "d-none";
|
||||
default:
|
||||
return "d-none d-xl-grid";
|
||||
}
|
||||
},
|
||||
},
|
||||
template: /*html*/`
|
||||
<div
|
||||
@@ -58,8 +73,9 @@ export default {
|
||||
@wheel.stop
|
||||
>
|
||||
<div
|
||||
v-if="!event.allDayEvent && event?.beginn && event?.ende"
|
||||
class="event-time d-none d-xl-grid h-100"
|
||||
v-if="!event?.allDayEvent && event?.beginn && event?.ende"
|
||||
:class="timeSlotDisplayClasses"
|
||||
class="event-time h-100"
|
||||
>
|
||||
<span>{{ start }}</span>
|
||||
<span>{{ end }}</span>
|
||||
|
||||
@@ -3,6 +3,11 @@ export default {
|
||||
event: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
timeSlotDisplayBehavior: {
|
||||
type: String,
|
||||
default: "default",
|
||||
// options: default, always, never
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -50,21 +55,33 @@ export default {
|
||||
return luxon.Duration
|
||||
.fromISOTime(this.event.ende)
|
||||
.toISOTime({ suppressSeconds: true });
|
||||
}
|
||||
},
|
||||
timeSlotDisplayClasses() {
|
||||
switch (this.$props.timeSlotDisplayBehavior) {
|
||||
case "always":
|
||||
return "d-grid";
|
||||
case "never":
|
||||
return "d-none";
|
||||
default:
|
||||
return "d-none d-xl-grid";
|
||||
}
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="cis-renderer-reservierungen-calendar-event calendar-event-default h-100 w-100 p-1"
|
||||
>
|
||||
<div
|
||||
v-if="!event.allDayEvent && event?.beginn && event?.ende"
|
||||
class="event-time d-grid h-100"
|
||||
v-if="!event?.allDayEvent && event?.beginn && event?.ende"
|
||||
:class="timeSlotDisplayClasses"
|
||||
class="event-time h-100"
|
||||
>
|
||||
<span>{{ start }}</span>
|
||||
<span>{{ end }}</span>
|
||||
</div>
|
||||
<div class="event-text" v-tooltip="tooltipString">
|
||||
<span class="event-topic">{{ event.topic }}</span>
|
||||
<span class="event-place">{{ event.ort_kurzbz }}</span>
|
||||
<span
|
||||
v-for="lektor in event.lektor.slice(0, 3)"
|
||||
class="event-lectors"
|
||||
@@ -77,7 +94,6 @@ export default {
|
||||
>
|
||||
... +{{ event.lektor.length - 3 }}
|
||||
</span>
|
||||
<span class="event-place">{{ event.ort_kurzbz }}</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
@@ -26,13 +26,13 @@ export default {
|
||||
internMail(event) {
|
||||
if (this.internMails.length)
|
||||
{
|
||||
splitMailsHelper(this.internMails, event, null, this.$fhcAlert, this.$p)
|
||||
splitMailsHelper(this.internMails, event, null, null, this.$fhcAlert, this.$p)
|
||||
}
|
||||
},
|
||||
privateMail(event) {
|
||||
if (this.privateMails.length)
|
||||
{
|
||||
splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p)
|
||||
splitMailsHelper(this.privateMails, event, null,null, this.$fhcAlert, this.$p)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import PvAutoComplete from "../../../../../../../index.ci.php/public/js/componen
|
||||
import ApiStvProjektarbeit from '../../../../../api/factory/stv/projektarbeit.js';
|
||||
|
||||
export default {
|
||||
name: 'ProjektarbeitDetails',
|
||||
components: {
|
||||
FormForm,
|
||||
FormInput,
|
||||
@@ -110,6 +111,10 @@ export default {
|
||||
this.formData.anmerkung = null;
|
||||
this.$refs.formDetails.clearValidation();
|
||||
},
|
||||
setFormData(projektarbeit) {
|
||||
this.formData = projektarbeit;
|
||||
if (this.formData.firma_id) this.formData.firma = {firma_id: this.formData.firma_id, name: this.formData.firma_name};
|
||||
},
|
||||
getFormData(newProjektarbeit, studiensemester_kurzbz, additional_lehrveranstaltung_id) {
|
||||
|
||||
this.additional_lehrveranstaltung_id = additional_lehrveranstaltung_id;
|
||||
@@ -148,8 +153,7 @@ export default {
|
||||
return this.$api
|
||||
.call(ApiStvProjektarbeit.loadProjektarbeit(projektarbeit_id))
|
||||
.then(result => {
|
||||
this.formData = result.data;
|
||||
if (this.formData.firma_id) this.formData.firma = {firma_id: this.formData.firma_id, name: this.formData.firma_name};
|
||||
this.setFormData(result.data)
|
||||
return result;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
|
||||
+42
-59
@@ -9,6 +9,7 @@ import ProjektarbeitDetails from "./Details.js";
|
||||
import Projektbetreuer from "./Projektbetreuer.js";
|
||||
|
||||
export default {
|
||||
name: 'Projektarbeit',
|
||||
components: {
|
||||
CoreFilterCmpt,
|
||||
BsModal,
|
||||
@@ -213,17 +214,6 @@ export default {
|
||||
});
|
||||
container.append(button);
|
||||
|
||||
button = document.createElement('button');
|
||||
button.className = 'btn btn-outline-secondary btn-action';
|
||||
button.innerHTML = '<i class="fa fa-users"></i>';
|
||||
button.title = this.$p.t('projektarbeit', 'betreuerBearbeiten');
|
||||
button.addEventListener('click', (event) => {
|
||||
let data = cell.getData();
|
||||
this.editedProjektarbeit = data;
|
||||
this.actionEditBetreuer();
|
||||
});
|
||||
container.append(button);
|
||||
|
||||
button = document.createElement('button');
|
||||
button.className = 'btn btn-outline-secondary btn-action';
|
||||
button.innerHTML = '<i class="fa fa-xmark"></i>';
|
||||
@@ -264,6 +254,7 @@ export default {
|
||||
actionEditProjektarbeit() {
|
||||
this.statusNew = false;
|
||||
this.toggleMenu('details');
|
||||
this.$refs.projektbetreuer.getProjektbetreuer(this.editedProjektarbeit?.projektarbeit_id, this.editedProjektarbeit?.studiensemester_kurzbz);
|
||||
this.$refs.projektarbeitModal.show();
|
||||
},
|
||||
actionEditBetreuer() {
|
||||
@@ -280,18 +271,26 @@ export default {
|
||||
.then(this.deleteProjektarbeit)
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
saveProjektarbeit() {
|
||||
if(this.statusNew) this.addNewProjektarbeit()
|
||||
else this.updateProjektarbeit()
|
||||
},
|
||||
addNewProjektarbeit() {
|
||||
this.$refs.projektarbeitDetails.addNewProjektarbeit()
|
||||
.then((result) => {
|
||||
if(result?.data?.length) {
|
||||
this.editedProjektarbeit = result.data[0]
|
||||
this.$refs.projektarbeitDetails.setFormData(this.editedProjektarbeit)
|
||||
this.toggleMenu('betreuer');
|
||||
}
|
||||
this.projektarbeitSaved();
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
updateProjektarbeit() {
|
||||
this.$refs.projektarbeitDetails.updateProjektarbeit()
|
||||
.then((result) => {
|
||||
this.projektarbeitSaved();
|
||||
})
|
||||
.then(() => this.$refs.projektbetreuer.saveIfOpen())
|
||||
.then(() => this.projektarbeitSaved())
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
deleteProjektarbeit(projektarbeit_id) {
|
||||
@@ -308,7 +307,8 @@ export default {
|
||||
projektarbeitSaved() {
|
||||
this.reload();
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
|
||||
this.hideModal('projektarbeitModal');
|
||||
if(!this.statusNew) this.hideModal('projektarbeitModal');
|
||||
else this.statusNew = false
|
||||
},
|
||||
setDefaultStunden(projekttyp_kurzbz) {
|
||||
this.$refs.projektbetreuer.setDefaultStunden(projekttyp_kurzbz);
|
||||
@@ -321,22 +321,22 @@ export default {
|
||||
},
|
||||
toggleMenu(tabId) {
|
||||
this.activeTab = tabId;
|
||||
if (this.statusNew == false) {
|
||||
switch(tabId) {
|
||||
case 'details':
|
||||
this.$refs.projektarbeitDetails.getFormData(
|
||||
this.statusNew, this.editedProjektarbeit?.studiensemester_kurzbz, this.editedProjektarbeit?.lehrveranstaltung_id
|
||||
);
|
||||
this.$refs.projektarbeitDetails.loadProjektarbeit(this.editedProjektarbeit?.projektarbeit_id);
|
||||
break;
|
||||
case 'betreuer':
|
||||
this.$refs.projektbetreuer.getFormData(
|
||||
this.editedProjektarbeit ? this.editedProjektarbeit.projekttyp_kurzbz : null
|
||||
);
|
||||
this.$refs.projektbetreuer.getProjektbetreuer(this.editedProjektarbeit?.projektarbeit_id, this.editedProjektarbeit?.studiensemester_kurzbz);
|
||||
break;
|
||||
}
|
||||
if (this.statusNew == false && tabId == 'details') {
|
||||
|
||||
this.$refs.projektarbeitDetails.getFormData(
|
||||
this.statusNew, this.editedProjektarbeit?.studiensemester_kurzbz, this.editedProjektarbeit?.lehrveranstaltung_id
|
||||
);
|
||||
this.$refs.projektarbeitDetails.loadProjektarbeit(this.editedProjektarbeit?.projektarbeit_id);
|
||||
} else if(tabId == 'betreuer') {
|
||||
this.$refs.projektbetreuer.getFormData(
|
||||
this.editedProjektarbeit ? this.editedProjektarbeit.projekttyp_kurzbz : null
|
||||
);
|
||||
this.$refs.projektbetreuer.getProjektbetreuer(this.editedProjektarbeit?.projektarbeit_id, this.editedProjektarbeit?.studiensemester_kurzbz);
|
||||
}
|
||||
},
|
||||
resetFormData() {
|
||||
this.$refs.projektarbeitDetails.resetForm()
|
||||
this.$refs.projektbetreuer.resetForm()
|
||||
}
|
||||
},
|
||||
template: `
|
||||
@@ -358,46 +358,29 @@ export default {
|
||||
</core-filter-cmpt>
|
||||
|
||||
<!--Modal: projektarbeitModal-->
|
||||
<bs-modal ref="projektarbeitModal" dialog-class="modal-xl modal-dialog-scrollable" header-class="flex-wrap pb-0">
|
||||
<bs-modal ref="projektarbeitModal" :dialog-class="(statusNew ? 'modal-xl ' : 'fhc-xxl-modal ' ) + 'modal-dialog-scrollable'"
|
||||
header-class="flex-wrap pb-0"
|
||||
@hideBsModal="resetFormData"
|
||||
>
|
||||
<template #title>
|
||||
<p v-if="statusNew" class="fw-bold mt-3">{{$p.t('projektarbeit', 'projektarbeitAnlegen')}}</p>
|
||||
<p v-else class="fw-bold mt-3">{{$p.t('projektarbeit', 'projektarbeitBearbeiten')}}</p>
|
||||
</template>
|
||||
|
||||
<template #modal-header-content v-if="!statusNew">
|
||||
<ul class="nav nav-tabs w-100 mt-3 msg_preview" id="pa_tabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" :class="activeTab == 'details' ? 'active' : ''" id="details-tab" data-bs-toggle="tab" data-bs-target="#details" type="button" role="tab" aria-controls="details" aria-selected="true" @click="toggleMenu('details')">Details</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" :class="activeTab == 'betreuer' ? 'active' : ''" id="betreuer-tab" data-bs-toggle="tab" data-bs-target="#betreuer" type="button" role="tab" aria-controls="betreuer" aria-selected="false" @click="toggleMenu('betreuer')">{{$p.t('projektarbeit', 'betreuerGross')}}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<div class="tab-content" id="pa_content">
|
||||
<div class="tab-pane fade show" :class="activeTab == 'details' ? 'active' : ''" id="details" role="tabpanel" aria-labelledby="details-tab">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<projektarbeit-details ref="projektarbeitDetails" :student="student" @projekttyp-changed="setDefaultStunden">
|
||||
</projektarbeit-details>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" >
|
||||
<div :class="statusNew ? 'col-12' : 'col-6'">
|
||||
<projektarbeit-details ref="projektarbeitDetails" :student="student" @projekttyp-changed="setDefaultStunden">
|
||||
</projektarbeit-details>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade show" :class="activeTab == 'betreuer' ? 'active' : ''" id="betreuer" role="tabpanel" aria-labelledby="betreuer-tab">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<projektbetreuer ref="projektbetreuer" :config="config" @betreuer-saved="reload"></projektbetreuer>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="statusNew ? '' : 'col-6'" :style="statusNew ? 'display: none' : ''">
|
||||
<projektbetreuer ref="projektbetreuer" :config="config" @betreuer-saved="reload"></projektbetreuer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{$p.t('ui', 'abbrechen')}}</button>
|
||||
<button v-if="statusNew" class="btn btn-primary" @click="addNewProjektarbeit()"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-if="!statusNew && activeTab == 'details'" class="btn btn-primary" @click="updateProjektarbeit()"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button type="button" class="btn btn-secondary" @click="resetFormData" data-bs-dismiss="modal">{{$p.t('ui', 'abbrechen')}}</button>
|
||||
<button class="btn btn-primary" @click="saveProjektarbeit()"> {{$p.t('ui', 'speichern')}}</button>
|
||||
</template>
|
||||
|
||||
</bs-modal>
|
||||
|
||||
+16
-10
@@ -10,6 +10,7 @@ import Vertrag from "./Vertrag.js";
|
||||
import ApiStvProjektbetreuer from '../../../../../api/factory/stv/projektbetreuer.js';
|
||||
|
||||
export default {
|
||||
name: 'Projektbetreuer',
|
||||
components: {
|
||||
CoreFilterCmpt,
|
||||
BsModal,
|
||||
@@ -296,17 +297,25 @@ export default {
|
||||
this.emptyBetreuerList();
|
||||
}
|
||||
},
|
||||
saveProjektbetreuer() {
|
||||
this.$refs.formProjektbetreuer.call(
|
||||
_doSaveBetreuer() {
|
||||
return this.$refs.formProjektbetreuer.call(
|
||||
ApiStvProjektbetreuer.saveProjektbetreuer(this.projektarbeit_id, this.getFormDataWithBetreuer())
|
||||
)
|
||||
.then(result => {
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
|
||||
).then(result => {
|
||||
this.getProjektbetreuer(this.projektarbeit_id, this.studiensemester_kurzbz);
|
||||
this.resetModes();
|
||||
this.$emit('betreuerSaved');
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
return result;
|
||||
});
|
||||
},
|
||||
// called by combined save button
|
||||
saveIfOpen() {
|
||||
if (!this.betreuerFormOpened) return Promise.resolve(null);
|
||||
return this._doSaveBetreuer();
|
||||
},
|
||||
saveProjektbetreuer() {
|
||||
this._doSaveBetreuer()
|
||||
.then(() => this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')))
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
searchBetreuer(event) {
|
||||
if (this.abortController.betreuer) {
|
||||
@@ -538,9 +547,6 @@ export default {
|
||||
|
||||
</form-form>
|
||||
|
||||
<button class="btn btn-primary" v-show="betreuerFormOpened" @click="saveProjektbetreuer">
|
||||
{{ $p.t('projektarbeit', 'betreuerSpeichern') }}
|
||||
</button>
|
||||
<!-- <div class = "mt-5" v-if="beurteilungDownloadLink !== null">
|
||||
<div class="mb-1">
|
||||
<a :href="beurteilungDownloadLink" class="btn btn-primary d-block" :class="{ 'disabled' : beurteilungDownloadLink === ''}">
|
||||
|
||||
@@ -220,6 +220,10 @@ export const CoreFilterCmpt = {
|
||||
else
|
||||
this.getFilter();
|
||||
},
|
||||
setSelectedFields() {
|
||||
const cols = this.tabulator.getColumns();
|
||||
this.selectedFields = cols.filter(col => col.isVisible()).map(col => col.getField());
|
||||
},
|
||||
async initTabulator() {
|
||||
let placeholder = '< Phrasen Plugin not loaded! >';
|
||||
if (this.$p) {
|
||||
@@ -337,7 +341,7 @@ export const CoreFilterCmpt = {
|
||||
this.tabulator.on('tableBuilt', () => {
|
||||
const cols = this.tabulator.getColumns();
|
||||
this.fields = cols.map(col => col.getField());
|
||||
this.selectedFields = cols.filter(col => col.isVisible()).map(col => col.getField());
|
||||
this.setSelectedFields();
|
||||
if (this.tabulator.options.persistence.headerFilter)
|
||||
this._setHeaderFilter();
|
||||
});
|
||||
@@ -371,6 +375,7 @@ export const CoreFilterCmpt = {
|
||||
});
|
||||
this.tabulator.clearFilter();
|
||||
this.filterActive = false;
|
||||
this.$emit('headerFilterOn', this.filterActive)
|
||||
},
|
||||
_setHeaderFilter()
|
||||
{
|
||||
|
||||
@@ -1,45 +1,67 @@
|
||||
export async function splitMailsHelper(mails, event, subject, alertPluginRef, phrasenPluginRef) {
|
||||
export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef) {
|
||||
await phrasenPluginRef.loadCategory('ui');
|
||||
debugger
|
||||
let splititem = ",";
|
||||
let maillist = mails.join(splititem);
|
||||
let mailto = "";
|
||||
// take subject line length + '?subject=' length into account
|
||||
const subjectlength = subject && typeof subject === 'string' ? subject.length + 9 : 0
|
||||
if (maillist.length > 2024)
|
||||
{
|
||||
if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'zuvieleEMails') }) === false)
|
||||
return;
|
||||
let useBcc = event?.ctrlKey || event?.metaKey;
|
||||
|
||||
// build query parameters using URLSearchParams to get encoding
|
||||
const urlParams = new URLSearchParams();
|
||||
if (subject && typeof subject === 'string') {
|
||||
urlParams.append('subject', subject);
|
||||
}
|
||||
if (body && typeof body === 'string') {
|
||||
urlParams.append('body', body);
|
||||
}
|
||||
|
||||
// initial overhead: "mailto:?bcc=" -> 12 chars, "mailto:" -> 7 chars
|
||||
const baseOverhead = useBcc ? 12 : 7;
|
||||
let queryString = urlParams.toString().replace(/\+/g, '%20');;
|
||||
let overhead = baseOverhead + (queryString ? 1 + queryString.length : 0); // +1 accounts for '?' or '&'
|
||||
|
||||
// calculate overhead with body to exceed the limit
|
||||
if (overhead > 2024) {
|
||||
await alertPluginRef.alertWarning(phrasenPluginRef.t('ui', 'bodyZuLang'));
|
||||
urlParams.delete('body').replace(/\+/g, '%20');;
|
||||
queryString = urlParams.toString();
|
||||
overhead = baseOverhead + (queryString ? 1 + queryString.length : 0);
|
||||
}
|
||||
|
||||
let firstrun = true;
|
||||
let useBcc = event?.ctrlKey || event?.metaKey;
|
||||
while (maillist.length > 0)
|
||||
{
|
||||
if (maillist.length + subjectlength > 2024)
|
||||
{
|
||||
let splitposition = maillist.lastIndexOf(splititem, 1900);
|
||||
while (maillist.length > 0) {
|
||||
let mailto = "";
|
||||
if (maillist.length + overhead > 2024) {
|
||||
let splitposition = maillist.lastIndexOf(splititem, 2024 - overhead);
|
||||
|
||||
// Fallback guard: if a single email address is somehow longer than the remaining space
|
||||
if (splitposition === -1) {
|
||||
splitposition = maillist.indexOf(splititem);
|
||||
if (splitposition === -1) splitposition = maillist.length;
|
||||
}
|
||||
|
||||
mailto = maillist.substring(0, splitposition);
|
||||
maillist = maillist.substring(splitposition + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
mailto = maillist;
|
||||
maillist = "";
|
||||
}
|
||||
|
||||
// construct the clean mailLink
|
||||
let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`;
|
||||
if(subject && typeof subject === 'string') mailLink += `?subject=${subject}`
|
||||
if (firstrun)
|
||||
{
|
||||
window.location.href = mailLink;
|
||||
firstrun = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'weitereEMail')}) === true)
|
||||
{
|
||||
window.location.href = mailLink;
|
||||
}
|
||||
if (queryString) {
|
||||
// If using BCC, the string already has a '?', so append with '&'. Otherwise, start with '?'
|
||||
mailLink += useBcc ? `&${queryString}` : `?${queryString}`;
|
||||
}
|
||||
|
||||
if (firstrun) {
|
||||
window.location.href = mailLink;
|
||||
firstrun = false;
|
||||
} else {
|
||||
if (await alertPluginRef.confirm({message: phrasenPluginRef.t('ui', 'weitereEMail')}) === true) {
|
||||
window.location.href = mailLink;
|
||||
} else {
|
||||
break; // Stop processing further batches if the user cancels
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34544,7 +34544,7 @@ array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Sie können eine Entschuldigung für einen Zeitraum von bis zu {0} Tagen in die Vergangenheit und bis zum Ende des aktuellen Semesters hochladen. Die erlaubten Dateitypen für das Dokument sind .pdf, .png und .jpg.
|
||||
|
||||
Sobald Sie eine Entschuldigung hochladen wird die zugehörige Studiengangsassistenz informiert und Ihr Anliegen überprüft. Solange Ihre Entschuldigung noch keinen akzeptierten oder abgelehnten Status erhalten hat, steht es Ihnen frei diese inklusive Datei zu löschen. Sobald sie entweder akzeptiert oder abgelehnt wurde können Sie den Eintrag nichtmehr löschen.
|
||||
Sobald Sie eine Entschuldigung hochladen wird die zugehörige Studiengangsassistenz informiert und Ihr Anliegen überprüft. Solange Ihre Entschuldigung noch keinen akzeptierten oder abgelehnten Status erhalten hat, steht es Ihnen frei diese inklusive Datei zu löschen. Sobald sie entweder akzeptiert oder abgelehnt wurde können Sie den Eintrag nicht mehr löschen.
|
||||
|
||||
Bei einer akzeptierten Entschuldigung werden sämtliche digitalen Anwesenheiten in diesem Zeitraum als positiv gewertet. Eine abgelehnte Entschuldigung hat keine Auswirkungen auf ihre Anwesenheitsquote.',
|
||||
'description' => '',
|
||||
@@ -42204,12 +42204,12 @@ array(
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'abgabetool',
|
||||
'phrase' => 'c4fehlerAktualitaetProjektarbeit',
|
||||
'phrase' => 'c4fehlerAktualitaetProjektarbeitv2',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => "Projektarbeit ist nichtmehr aktuell",
|
||||
'text' => "Projektarbeit ist nicht mehr aktuell",
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
@@ -47364,6 +47364,66 @@ array(
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'abgabetool',
|
||||
'phrase' => 'c4signaturinfo',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Digitale Signatur Leitfaden',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Guidelines for digital signatures',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'abgabetool',
|
||||
'phrase' => 'c4emptyThesisTitle',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Projektarbeit Titel darf nicht leer sein.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Projekt work title is not allowed to be empty.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'abgabetool',
|
||||
'phrase' => 'c4invalidCharactersThesisTitle',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Ungültige Zeichen im Titel der Projektarbeit gefunden.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Invalid characters detected in thesis title.',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
// ABGABETOOL PHRASEN END
|
||||
array(
|
||||
'app' => 'core',
|
||||
|
||||
Reference in New Issue
Block a user