diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php
index 43dc18d1c..d6f3c29a8 100644
--- a/application/controllers/api/frontend/v1/Abgabe.php
+++ b/application/controllers/api/frontend/v1/Abgabe.php
@@ -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,
@@ -448,7 +451,194 @@ 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()
+ {
+ $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');
+ }
+
+ $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' => trim($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' => trim($titel),
+ 'updatevon' => getAuthUID(),
+ 'updateamum' => date('Y-m-d H:i:s')
+ ),
+ getAuthUID(),
+ getAuthPersonId()
+ ));
+
+ $this->sendTitelChangedEmail(
+ $projektarbeit_id,
+ trim($titel),
+ $oldTitle,
+ $assignedStudentUid
+ );
+
+ $result = $this->ProjektarbeitModel->load($projektarbeit_id);
+ $this->terminateWithSuccess($result);
+ }
+
+ /**
+ * 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 +877,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 +1002,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
@@ -1411,7 +1738,13 @@ 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','c4fehlerAktualitaetProjektarbeit');
+ 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);
+ }
}
}
diff --git a/application/models/education/Projektarbeit_model.php b/application/models/education/Projektarbeit_model.php
index 3b1ea55e5..ef1759154 100644
--- a/application/models/education/Projektarbeit_model.php
+++ b/application/models/education/Projektarbeit_model.php
@@ -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));
diff --git a/application/views/Cis/Abgabetool.php b/application/views/Cis/Abgabetool.php
index 86e8721f2..a0621b1f9 100644
--- a/application/views/Cis/Abgabetool.php
+++ b/application/views/Cis/Abgabetool.php
@@ -38,7 +38,7 @@ $includesArray = array(
$this->load->view('templates/FHC-Header', $includesArray);
?>
-
+
uid=
student_uid_prop=""
stg_kz_prop=""
diff --git a/public/css/components/abgabetool/abgabe.css b/public/css/components/abgabetool/abgabe.css
index c7e18f728..8b25cbae6 100644
--- a/public/css/components/abgabetool/abgabe.css
+++ b/public/css/components/abgabetool/abgabe.css
@@ -329,4 +329,20 @@
/*conditional tooltips fix*/
.p-tooltip.custom-tooltip {
z-index: 8001 !important;
-}
\ No newline at end of file
+}
+
+ /* 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 {
+ font-size: 0.5rem;
+ }
+
+ .tabulator-cell, .tabulator-row {
+ height: 20px;
+ max-height: 20px;
+ }
+
+}
+
diff --git a/public/js/api/factory/abgabe.js b/public/js/api/factory/abgabe.js
index c6f229973..7621b548b 100644
--- a/public/js/api/factory/abgabe.js
+++ b/public/js/api/factory/abgabe.js
@@ -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},
+
};
}
};
\ No newline at end of file
diff --git a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js
index a7931f307..04664c0ec 100644
--- a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js
+++ b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js
@@ -31,12 +31,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 +51,52 @@ export const AbgabeStudentDetail = {
}
},
methods: {
+ openTitelEdit() {
+ this.editingTitel = this.projektarbeit.titel ?? '';
+ this.$refs.modalTitelEdit.show();
+ },
+ async saveTitel() {
+ const trimmed = this.editingTitel.trim();
+ if (!trimmed) {
+ this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningEmptyField')));
+ return;
+ }
+
+ 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,
+ trimmed
+ )
+ ).then(res => {
+ if (res.meta.status === 'success') {
+ this.projektarbeit.titel = trimmed;
+ this.$emit('titel-updated', {
+ projektarbeit_id: this.projektarbeit.projektarbeit_id,
+ titel: trimmed
+ });
+ 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 +110,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 +122,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 +139,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]);
}
@@ -110,23 +155,21 @@ export const AbgabeStudentDetail = {
.then(res => {
this.handleUploadRes(res, this.enduploadTermin)
}).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 month = padZero(date.getMonth() + 1);
const day = padZero(date.getDate());
const year = date.getFullYear();
@@ -134,14 +177,12 @@ export const AbgabeStudentDetail = {
},
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 +192,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 +202,7 @@ export const AbgabeStudentDetail = {
.then(res => {
this.handleUploadRes(res, termin)
}).finally(()=> {
- this.loading = false
+ this.loading = false
})
}
},
@@ -169,21 +210,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];
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 +233,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 +240,13 @@ export const AbgabeStudentDetail = {
this.form.schlagwoerter_en = newVal.schlagwoerter_en ?? ''
this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter ?? ''
this.form.seitenanzahl = newVal.seitenanzahl ?? 1
-
}
},
computed: {
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 +279,47 @@ export const AbgabeStudentDetail = {
})
return qgatefound
},
+ isTitelEditAllowed() {
+ // blocked once the projektarbeit has a note (finished) - mirrors backend guard
+ return !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: `
@@ -332,9 +329,24 @@ export const AbgabeStudentDetail = {
{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereich') )}}
-
{{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}
-
{{$capitalize( $p.t('abgabetool/c4titel') ) }}: {{projektarbeit?.titel}}
-
{{$capitalize( $p.t('abgabetool/c4betreuerv2') ) }}: {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}
+
{{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}
+
+
+ {{$capitalize( $p.t('abgabetool/c4titel') ) }}: {{projektarbeit?.titel}}
+
+
+
+
{{$capitalize( $p.t('abgabetool/c4betreuerv2') ) }}: {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}
{{ $p.t('abgabetool/c4checkoutStgMoodleInfos') }}
@@ -357,7 +369,6 @@ export const AbgabeStudentDetail = {
-
{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}
@@ -408,8 +419,6 @@ export const AbgabeStudentDetail = {
-
-
@@ -481,9 +490,6 @@ export const AbgabeStudentDetail = {
{{ $p.t('abgabetool/c4keineSignatur') }}
{{ $p.t('abgabetool/c4signaturServerError') }}
-
-
-
@@ -542,8 +548,49 @@ export const AbgabeStudentDetail = {
{{ $capitalize( $p.t('abgabetool/c4keineAbgabetermineGefunden') )}}
-
-
+
+
+
+
+ {{$capitalize( $p.t('abgabetool/c4titelBearbeiten') )}}
+
+
+
+
+
+
{{ editingTitel.length }} / 1024
+
+
+
+
+
+
+
+
-
Student UID: {{ projektarbeit?.student_uid}}
-
-
{{$capitalize( $p.t('abgabetool/c4titel') )}}: {{ projektarbeit?.titel }}
-
@@ -576,15 +619,6 @@ export const AbgabeStudentDetail = {
-
-
-
-
-
-
-
-
-
{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}
@@ -631,7 +665,6 @@ export const AbgabeStudentDetail = {
{{$capitalize( $p.t('abgabetool/c4gelesenUndAkzeptiert') )}}
-
{{$capitalize( $p.t('ui/hochladen') )}}
-
`,
};
-export default AbgabeStudentDetail;
+export default AbgabeStudentDetail;
\ No newline at end of file
diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js
index 9df53d106..32a641096 100644
--- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js
+++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js
@@ -53,6 +53,7 @@ export const AbgabetoolAssistenz = {
},
data() {
return {
+ flatDataDirty: true,
mode: 'perProjectView',
qgate1FilterSelected: [],
qgate2FilterSelected: [],
@@ -73,6 +74,11 @@ export const AbgabetoolAssistenz = {
colLayoutRestored: false,
sortRestored: false,
stateRestored: false,
+ headerFiltersRestoredFlat: false,
+ filtersRestoredFlat: false,
+ colLayoutRestoredFlat: false,
+ sortRestoredFlat: false,
+ stateRestoredFlat: false,
timelineProjekt: null,
selectedStudiengangOption: null,
studiengaengeOptions: null,
@@ -91,6 +97,22 @@ export const AbgabetoolAssistenz = {
allowedNotenFilterOptions: null,
allowedNotenOptions: null,
notenOptionsNonFinal: null,
+ serienEdit: Vue.reactive({
+ datum: null,
+ bezeichnung: null,
+ kurzbz: null,
+ upload_allowed: null,
+ fixtermin: null,
+ invertedFixtermin: null,
+ }),
+ // track which fields should actually be applied
+ serienEditFields: {
+ datum: false,
+ bezeichnung: false,
+ kurzbz: false,
+ upload_allowed: false,
+ fixtermin: false,
+ },
serienTermin: Vue.reactive({
datum: new Date().toISOString().split('T')[0],
bezeichnung: {
@@ -124,7 +146,6 @@ export const AbgabetoolAssistenz = {
placeholder: Vue.computed(() => this.$capitalize(this.$p.t('global/noDataAvailable'))),
selectable: true,
selectableCheck: this.selectionCheck,
- rowHeight: 40,
renderVerticalBuffer: 2000,
columns: [
{
@@ -294,12 +315,12 @@ export const AbgabetoolAssistenz = {
],
abgabeTableOptionsFlat: {
minHeight: 250,
+ height: 700,
index: 'projektarbeit_id',
- layout: 'fitData',
+ layout: 'fitColumns',
placeholder: Vue.computed(() => this.$capitalize(this.$p.t('global/noDataAvailable'))),
selectable: true,
- rowHeight: 40,
- renderVerticalBuffer: 2000,
+ renderVerticalBuffer: 400,
columns: [
{
formatter: function (cell, formatterParams, onRendered) {
@@ -347,7 +368,7 @@ export const AbgabetoolAssistenz = {
hozAlign: "center",
headerSort: false,
formatterParams: {
- handleClick: this.selectHandlerFlat
+ handleClick: this.selectHandler
},
titleFormatterParams: {
handleClick: this.selectAllHandlerFlat
@@ -355,26 +376,25 @@ export const AbgabetoolAssistenz = {
width: 50,
cssClass: 'sticky-col'
},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, minWidth: 140, tooltip: false, visible: false},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1, minWidth: 100, visible: false},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1, minWidth: 100, visible: true},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4studstatus'))), field: 'studienstatus', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1, minWidth: 150, visible: false},
- {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4orgformv2'))), field: 'orgform', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1, minWidth: 50, visible: false},
-
- // --- termin data ---
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, minWidth: 140, tooltip: false, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: true},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4studstatus'))), field: 'studienstatus', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 150, visible: false},
+ {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4orgformv2'))), field: 'orgform', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: false},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabetyp'))),
field: 'paabgabetyp_kurzbz',
headerFilter: true,
- formatter: this.centeredTextFormatter,
- minWidth: 120, widthGrow: 1
+ formatter: this.paabgabetypFormatter,
+
+ minWidth: 120,
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabekurzbzv2'))),
field: 'kurzbz',
headerFilter: true,
formatter: this.centeredTextFormatter,
- minWidth: 120, widthGrow: 1
+ minWidth: 120
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zieldatumv2'))),
@@ -383,7 +403,7 @@ export const AbgabetoolAssistenz = {
headerFilterFunc: this.headerFilterTerminCol,
sorter: (a, b) => new Date(a) - new Date(b),
formatter: (cell) => this.formatDate(cell.getValue()),
- minWidth: 120, widthGrow: 1
+ minWidth: 100
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabedatum'))),
@@ -392,37 +412,47 @@ export const AbgabetoolAssistenz = {
headerFilterFunc: this.headerFilterTerminCol,
sorter: (a, b) => new Date(a) - new Date(b),
formatter: (cell) => this.formatDate(cell.getValue()),
- minWidth: 120, widthGrow: 1
+ minWidth: 100
},
-
- // --- status ---
{
- title: '',
+ title: 'Status',
field: 'dateStyle',
headerSort: false,
- formatter: (cell) => this.abgabterminFormatter(cell, true), // icon only mode
- width: 48,
+ headerFilter: this.statusHeaderFilterEditor,
+ headerFilterFunc: this.statusHeaderFilterFunc,
+ headerFilterParams: {},
+ formatter: this.abgabterminFormatter,
+ formatterParams: { iconOnly: true },
+ width: 70,
tooltip: (e, cell) => this.mapDateStyleToTabulatorTooltip(cell.getValue())
},
{
- title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))),
+ title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4noteprojektarbeit'))),
+ field: 'pa_note',
+ formatter: (cell) => {
+ const val = cell.getValue();
+ if (!val) return '';
+ return val?.bezeichnung ?? this.notenOptions?.find(n => n.note == val)?.bezeichnung ?? val;
+ },
+ minWidth: 100
+ },
+ {
+ title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4notetermin'))),
field: 'note',
formatter: (cell) => {
const val = cell.getValue();
if (!val) return '';
return val?.bezeichnung ?? this.notenOptions?.find(n => n.note == val)?.bezeichnung ?? val;
},
- minWidth: 100, widthGrow: 1
+ minWidth: 100
},
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4notizQualGatev2'))),
field: 'beurteilungsnotiz',
headerFilter: true,
formatter: this.centeredTextFormatter,
- minWidth: 150, widthGrow: 2, visible: false
+ minWidth: 150, visible: false
},
-
- // --- flags ---
{
title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4fixterminv4'))),
field: 'fixtermin',
@@ -443,7 +473,7 @@ export const AbgabetoolAssistenz = {
},
],
persistence: false,
- persistenceID: "abgabetool_2026_03_16"
+ persistenceID: "abgabetoolflat_2026_03_16"
},
abgabeTableEventHandlersFlat: [
{
@@ -473,17 +503,266 @@ export const AbgabetoolAssistenz = {
};
},
methods: {
+ reloadData() {
+ this.loadProjektarbeiten()
+ },
+ openEditModal() {
+ // reset
+ this.serienEditFields = {
+ datum: false,
+ bezeichnung: false,
+ kurzbz: false,
+ upload_allowed: false,
+ fixtermin: false,
+ }
+ this.serienEdit.datum = new Date().toISOString().split('T')[0]
+ this.serienEdit.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === 'zwischen')
+ this.serienEdit.kurzbz = ''
+ this.serienEdit.upload_allowed = false
+ this.serienEdit.invertedFixtermin = true
+
+ this.$refs.modalContainerEditSeries.show()
+ },
+ async handleEditSelectedTermine() {
+ const activeFields = Object.keys(this.serienEditFields).filter(k => this.serienEditFields[k])
+ if (!activeFields.length) {
+ this.$fhcAlert.alertWarning(this.$p.t('abgabetool/c4noFieldsSelected'))
+ return
+ }
+
+ if (await this.$fhcAlert.confirm({
+ message: this.$p.t('abgabetool/c4confirm_edit_n_termine', [this.selectedDataFlat.length]),
+ acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
+ acceptClass: 'p-button-primary',
+ rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
+ rejectClass: 'p-button-secondary'
+ }) === false) return
+
+ this.$refs.modalContainerEditSeries.hide()
+ this.editSelectedTermine(this.selectedDataFlat)
+ },
+ editSelectedTermine(termine) {
+ const paabgabeIDS = termine.map(t => t.paabgabe_id)
+
+ // only send fields that were checked
+ const payload = { paabgabe_ids: paabgabeIDS }
+ if (this.serienEditFields.datum) payload.datum = this.serienEdit.datum
+ if (this.serienEditFields.bezeichnung) payload.paabgabetyp_kurzbz = this.serienEdit.bezeichnung.paabgabetyp_kurzbz
+ if (this.serienEditFields.kurzbz) payload.kurzbz = this.serienEdit.kurzbz
+ if (this.serienEditFields.upload_allowed) payload.upload_allowed = this.serienEdit.upload_allowed
+ if (this.serienEditFields.fixtermin) payload.fixtermin = !this.serienEdit.invertedFixtermin
+
+ this.saving = true
+ this.$api.call(ApiAbgabe.patchProjektarbeitAbgabeMultiple(payload)).then(res => {
+ if (res?.meta?.status == 'success') {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui/gespeichert'))
+
+ // patch local data structure
+ termine.forEach(t => {
+ const pa = this.projektarbeiten.find(pa => pa.projektarbeit_id == t.projektarbeit_id)
+ const termin = pa.abgabetermine.find(termin => termin.paabgabe_id === t.paabgabe_id)
+ if (!termin) return
+
+ if (this.serienEditFields.datum) termin.datum = this.serienEdit.datum
+ if (this.serienEditFields.bezeichnung) termin.paabgabetyp_kurzbz = this.serienEdit.bezeichnung.paabgabetyp_kurzbz
+ if (this.serienEditFields.kurzbz) termin.kurzbz = this.serienEdit.kurzbz
+ if (this.serienEditFields.upload_allowed) termin.upload_allowed = this.serienEdit.upload_allowed
+ if (this.serienEditFields.fixtermin) termin.fixtermin = !this.serienEdit.invertedFixtermin
+ })
+
+ const updatedProjektarbeiten = new Set(termine.map(t => t.projektarbeit_id))
+ updatedProjektarbeiten.forEach(pa_id => {
+ const projektarbeit = this.projektarbeiten.find(pa => pa.projektarbeit_id == pa_id)
+ this.checkAbgabetermineProjektarbeit(projektarbeit)
+ })
+
+ this.redrawTableScrollSave()
+ this.selectedDataFlat = []
+ this.selectedcountFlat = 0
+ this.$refs.abgabeTableFlat.tabulator.setData(this.getAllTermine)
+
+ } else if (res?.meta?.status == 'error') {
+ this.$fhcAlert.alertError()
+ }
+ }).finally(() => {
+ this.saving = false
+ })
+ },
+ deleteSelectedTermine(termine) {
+ const paabgabeIDS = termine.map(t => t.paabgabe_id)
+ this.$api.call(ApiAbgabe.deleteProjektarbeitAbgabeMultiple(paabgabeIDS)).then( (res) => {
+ if(res?.meta?.status == 'success') {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui/genericDeleted', [this.$p.t('abgabetool/c4abgaben_n', [paabgabeIDS.length])]))
+
+ termine.forEach(t => {
+ const pa = this.projektarbeiten.find(pa => pa.projektarbeit_id == t.projektarbeit_id)
+ const deletedTerminIndex = pa.abgabetermine.findIndex(termin => t.paabgabe_id === termin.paabgabe_id)
+ pa.abgabetermine.splice(deletedTerminIndex, 1)
+ })
+
+ const updatedProjektarbeiten = new Set(termine.map(t => t.projektarbeit_id))
+
+ updatedProjektarbeiten.forEach(pa_id => {
+ const projektarbeit = this.projektarbeiten.find(pa => pa.projektarbeit_id == pa_id)
+ this.checkAbgabetermineProjektarbeit(projektarbeit)
+ })
+
+ this.redrawTableScrollSave()
+
+ // update flat table with fresh computed data and clear selection
+ this.selectedDataFlat = []
+ this.selectedcountFlat = 0
+ this.$refs.abgabeTableFlat.tabulator.setData(this.getAllTermine)
+
+ } else if(res?.meta?.status == 'error'){
+ this.$fhcAlert.alertError()
+ }
+ })
+ },
+ async handleDeleteSelectedTermine() {
+ // TODO: check if every selected termin is actually "allowed to delete"
+
+
+ if(await this.$fhcAlert.confirm({
+ message: this.$p.t('abgabetool/c4confirm_delete_n_termine', [this.selectedDataFlat.length]),
+ acceptLabel: 'Löschen',
+ acceptClass: 'p-button-danger',
+ rejectLabel: 'Zurück',
+ rejectClass: 'p-button-secondary'
+ }) === false) {
+ return false
+ } else {
+ this.deleteSelectedTermine(this.selectedDataFlat)
+ }
+
+ },
async switchMode() {
if(this.mode == 'perProjectView') {
this.mode = 'flatView'
await this.tableBuiltPromiseFlat;
-
- this.$refs.abgabeTableFlat.tabulator.setData(this.getAllTermine);
+
+ if(this.flatDataDirty) {
+ this.$refs.abgabeTableFlat.tabulator.setData(this.getAllTermine);
+ this.flatDataDirty = false
+ }
+
} else {
this.mode = 'perProjectView'
}
},
+ getDateStyleHtml(dateStyle) {
+ const iconMap = {
+ 'verspaetet': '',
+ 'verpasst': '',
+ 'abzugeben': '',
+ 'standard': '',
+ 'abgegeben': '',
+ 'beurteilungerforderlich': '',
+ 'bestanden': '',
+ 'nichtbestanden': '',
+ };
+ 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 = [
@@ -613,10 +892,12 @@ export const AbgabetoolAssistenz = {
},
toolTipFuncPrevTermin(e, cell, onRendered) {
const data = cell.getData();
+ if(!data.prevTermin) return ''
return this.mapDateStyleToTabulatorTooltip(data.prevTermin.dateStyle);
},
toolTipFuncNextTermin(e, cell, onRendered) {
const data = cell.getData();
+ if(!data.nextTermin) return ''
return this.mapDateStyleToTabulatorTooltip(data.nextTermin.dateStyle);
},
mapDateStyleToTabulatorTooltip(dateStyleString) {
@@ -722,20 +1003,31 @@ export const AbgabetoolAssistenz = {
const uniqueRecipients = [...new Set(recipientList)];
const subject = this.$p.t('abgabetool/c4sammelmailStudentBetreff', [this.selectedStudiengangOption?.bezeichnung]);
- splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p)
+ splitMailsHelper(uniqueRecipients, param.originalEvent, subject, null, this.$fhcAlert, this.$p)
},
sammelMailBetreuer(param) {
-
const recipientList = [];
this.selectedData.forEach(row => {
if (row.betreuer_mail) recipientList.push(row.betreuer_mail);
if (row.zweitbetreuer_mail) recipientList.push(row.zweitbetreuer_mail);
});
- // actually not necessary for email clients but looks better for assistenz if we avoid duplicates here
const uniqueRecipients = [...new Set(recipientList)];
const subject = this.$p.t('abgabetool/c4sammelmailBetreuerBetreff', [this.selectedStudiengangOption?.bezeichnung]);
- splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p)
+
+ // dedupe by student_uid, then build one line per student
+ const seenUids = new Set();
+ const bodyLines = [];
+ this.selectedData.forEach(row => {
+ if (seenUids.has(row.student_uid)) return;
+ seenUids.add(row.student_uid);
+ const name = `${row.student_vorname ?? ''} ${row.student_nachname ?? ''}`.trim();
+ const titel = row.titel ? ` - ${row.titel}` : '';
+ bodyLines.push(`${name}${titel}`);
+ });
+
+ const body = bodyLines.join('\n');
+ splitMailsHelper(uniqueRecipients, param.originalEvent, subject, body, this.$fhcAlert, this.$p)
},
selectHandler(e, cell) {
const row = cell.getRow();
@@ -768,6 +1060,24 @@ export const AbgabetoolAssistenz = {
e.stopPropagation();
return false;
},
+ selectAllHandlerFlat(e, cell) {
+ const table = cell.getTable();
+ const rows = this.filteredRowsFlat ?? table.getRows();
+
+ // custom select all logic
+ const allowed = rows.filter(r => r.getData().selectable);
+ const selected = table.getRows().every(r => r.isSelected());
+
+ if(selected){
+ allowed.forEach(r => r.deselect());
+ } else {
+ allowed.forEach(r => r.select());
+ }
+
+ // stop built-in handler
+ e.stopPropagation();
+ return false;
+ },
checkQualityGateStatus(projekt) {
const qgate1Termine = []
const qgate2Termine = []
@@ -994,7 +1304,7 @@ export const AbgabetoolAssistenz = {
table.on("renderComplete", () => {
if(!this.stateRestored) {
-
+
if (saved?.columns && !this.colLayoutRestored) {
const layout = saved.columns.map(col => ({
field: col.field,
@@ -1051,43 +1361,43 @@ export const AbgabetoolAssistenz = {
this.tableBuiltResolveFlat()
table.on("columnMoved", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
table.on("columnResized", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
table.on("columnVisibilityChanged", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
table.on("filterChanged", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
table.on("headerFilterChanged", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
table.on("dataSorted", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
table.on("columnSorted", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
table.on("sortersChanged", () => {
- this.saveState(table);
+ this.saveStateFlat(table);
});
- const saved = this.loadState();
+ const saved = this.loadStateFlat();
table.on("renderComplete", () => {
- if(!this.stateRestored) {
-
- if (saved?.columns && !this.colLayoutRestored) {
+ if(!this.stateRestoredFlat) {
+
+ if (saved?.columns && !this.colLayoutRestoredFlat) {
const layout = saved.columns.map(col => ({
field: col.field,
width: col.width,
@@ -1097,22 +1407,22 @@ export const AbgabetoolAssistenz = {
table.setColumnLayout(layout);
- this.colLayoutRestored = true;
+ this.colLayoutRestoredFlat = true;
}
- if (saved?.filters && !this.filtersRestored) {
- this.filtersRestored = true // instantly avoid retriggers
+ if (saved?.filters && !this.filtersRestoredFlat) {
+ this.filtersRestoredFlat = true // instantly avoid retriggers
table.setFilter(saved.filters);
}
- if (saved?.headerFilters && !this.headerFiltersRestored) {
- this.headerFiltersRestored = true // instantly avoid retriggers
+ if (saved?.headerFilters && !this.headerFiltersRestoredFlat) {
+ this.headerFiltersRestoredFlat = true // instantly avoid retriggers
for (let hf of saved.headerFilters) {
table.setHeaderFilterValue(hf.field, hf.value);
}
}
- if (saved?.sort?.length && !this.sortRestored) {
- this.sortRestored = true;
+ if (saved?.sort?.length && !this.sortRestoredFlat) {
+ this.sortRestoredFlat = true;
setTimeout(() => {
const sortList = saved.sort.map(s => {
@@ -1126,7 +1436,7 @@ export const AbgabetoolAssistenz = {
table.setSort(sortList);
}, 100);
}
- this.stateRestored = true
+ this.stateRestoredFlat = true
// ensure that the filterCollapseables thingy has the correct values
this.$refs.abgabeTableFlat.setSelectedFields();
@@ -1135,6 +1445,29 @@ export const AbgabetoolAssistenz = {
});
},
+ loadStateFlat() {
+ return JSON.parse(localStorage.getItem(this.abgabeTableOptionsFlat.persistenceID) || "null");
+ },
+ saveStateFlat(table) {
+ // avoid storing state after first restore part happened
+ if(!this.stateRestoredFlat) return
+ const rawLayout = table.getColumnLayout();
+ const state = {
+ columns: rawLayout.map(col => ({
+ field: col.field,
+ visible: col.visible,
+ width: col.width,
+ })),
+ sort: table.getSorters().map(s => ({
+ field: s.field,
+ dir: s.dir,
+ })),
+ filters: table.getFilters(),
+ headerFilters: table.getHeaderFilters()
+ };
+
+ localStorage.setItem(this.abgabeTableOptionsFlat.persistenceID, JSON.stringify(state));
+ },
handleToggleFullscreenDetail() {
this.detailIsFullscreen = !this.detailIsFullscreen
},
@@ -1273,6 +1606,12 @@ export const AbgabetoolAssistenz = {
this.projektarbeiten = this.mapProjekteToTableData(this.projektarbeiten)
this.redrawTableScrollSave()
+
+ // in case pesky user creates a series and instantly switches viewmode
+ this.flatDataDirty = true
+ if (this.mode === 'flatView') {
+ this.$refs.abgabeTableFlat.tabulator.setData(this.getAllTermine)
+ }
}).finally(()=>{
this.saving = false
@@ -1407,6 +1746,10 @@ export const AbgabetoolAssistenz = {
this.timelineProjekt = projekt
this.$refs.drawer.show()
},
+ paabgabetypFormatter(cell) {
+ const key = cell.getValue()
+ return this.$p.t('abgabetool/c4paatyp' + key)
+ },
centeredTextFormatter(cell) {
const longForm = cell.getValue()
if(!longForm) return
@@ -1440,12 +1783,14 @@ export const AbgabetoolAssistenz = {
return ''
},
- abgabterminFormatter(cell) {
+ abgabterminFormatter(cell, formatterParams, onRendered,) {
const val = cell.getValue()
-
+ const dateStyle = val?.dateStyle ?? val
+
+
if(val) {
let icon = ''
- switch(val.dateStyle) {
+ switch(dateStyle) {
case 'verspaetet':
icon = ''
break
@@ -1474,8 +1819,16 @@ export const AbgabetoolAssistenz = {
const bezeichnung = val.bezeichnung?.bezeichnung ?? val.bezeichnung
+ if(formatterParams?.iconOnly) {
+ return '' +
+ '' +
+ '
'
+ }
+
return '' +
- '