diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index bacf36bc9..f7775dc21 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -50,7 +50,8 @@ class Abgabe extends FHCAPI_Controller 'getStudentProjektarbeitAbgabeFile' => array('basis/abgabe_student:rw', 'basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'), 'postStudentProjektarbeitZusatzdaten' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'), 'getSignaturStatusForProjektarbeitAbgaben' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'), - 'sendZweitbetreuerTokenMail' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw') + 'sendZweitbetreuerTokenMail' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'), + 'fetchProjektarbeitenHistory' => array('basis/abgabe_assistenz:rw') ]); $this->load->library('PhrasesLib'); @@ -1549,6 +1550,37 @@ class Abgabe extends FHCAPI_Controller $this->sendUploadEmailZweitbegutachterToken($bperson_id, $projektarbeit_id, $student_uid); } + // basically what getStudentProjektarbeiten intends to do for the student page but + // designed for the assistenz timeline component -> more extensive loading upfront + // with every abgabetermin fetched here aswell + public function fetchProjektarbeitenHistory() { + $student_uid = $this->input->post('student_uid'); + $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); + + $projektarbeiten = $this->ProjektarbeitModel->getProjektarbeitenForStudent($student_uid); + + $mapFunc = function($projektarbeit) { + return $projektarbeit->projektarbeit_id; + }; + $projektarbeiten_ids = array_map($mapFunc, $projektarbeiten->retval); + + if(count($projektarbeiten_ids) > 0) { + $ret = $this->ProjektarbeitModel->getProjektarbeitenAbgabetermine($projektarbeiten_ids); + $projektabgaben = $this->getDataOrTerminateWithError($ret, 'general'); + } + + forEach($projektarbeiten->retval as $pa) { + + $filterFunc = function($projektabgabe) use ($pa) { + return $projektabgabe->projektarbeit_id == $pa->projektarbeit_id; + }; + + $pa->abgabetermine = array_values(array_filter($projektabgaben, $filterFunc)); + } + + $this->terminateWithSuccess($projektarbeiten->retval); + } + /** * helper function to check the signature status of uploaded files for zwischenabgabe & endupload */ diff --git a/application/models/education/Projektarbeit_model.php b/application/models/education/Projektarbeit_model.php index c317fa9ff..7b6febde1 100644 --- a/application/models/education/Projektarbeit_model.php +++ b/application/models/education/Projektarbeit_model.php @@ -187,7 +187,10 @@ class Projektarbeit_model extends DB_Model campus.tbl_paabgabetyp.bezeichnung, campus.tbl_paabgabetyp.benotbar, campus.tbl_paabgabe.abgabedatum, - campus.tbl_paabgabe.insertvon + campus.tbl_paabgabe.insertvon, + campus.tbl_paabgabe.insertamum, + campus.tbl_paabgabe.updatevon, + campus.tbl_paabgabe.updateamum FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz) WHERE campus.tbl_paabgabe.projektarbeit_id = ? ORDER BY campus.tbl_paabgabe.datum"; @@ -581,6 +584,129 @@ class Projektarbeit_model extends DB_Model return $this->execReadOnlyQuery($new_qry, array($studiengang_kz)); } + + public function getProjektarbeitenForStudent($student_uid) { + $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 + ) + + 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 + + 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_student.student_uid = ? + ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC + ) as tmp"; + + return $this->execReadOnlyQuery($new_qry, array($student_uid)); + } /** * diff --git a/public/css/components/abgabetool/abgabe.css b/public/css/components/abgabetool/abgabe.css index 78a04e114..af9578124 100644 --- a/public/css/components/abgabetool/abgabe.css +++ b/public/css/components/abgabetool/abgabe.css @@ -396,3 +396,13 @@ opacity: 0; } } + +.p-timeline-event-opposite { + flex: 0 0 90px; + max-width: 90px; +} + +.p-timeline-event-content { + flex: 1 1 auto; + min-width: 0; +} diff --git a/public/js/api/factory/abgabe.js b/public/js/api/factory/abgabe.js index 3c3f34fd0..2e582f742 100644 --- a/public/js/api/factory/abgabe.js +++ b/public/js/api/factory/abgabe.js @@ -169,6 +169,14 @@ export default { url: '/api/frontend/v1/Abgabe/sendZweitbetreuerTokenMail', params: {projektarbeit_id, bperson_id: betreuer_person_id, student_uid}, + }; + }, + fetchProjektarbeitenHistory(student_uid) { + return { + method: 'post', + url: '/api/frontend/v1/Abgabe/fetchProjektarbeitenHistory', + params: {student_uid}, + }; } }; \ No newline at end of file diff --git a/public/js/components/Cis/Abgabetool/AbgabeStudentTimeline.js b/public/js/components/Cis/Abgabetool/AbgabeStudentTimeline.js new file mode 100644 index 000000000..c75bb19aa --- /dev/null +++ b/public/js/components/Cis/Abgabetool/AbgabeStudentTimeline.js @@ -0,0 +1,251 @@ +import AbgabeterminStatusLegende from "./StatusLegende.js"; +import {formatDateTime} from "./dateUtils.js"; + +export const AbgabeStudentTimeline = { + name: "AbgabeStudentTimeline", + components: { + AbgabeterminStatusLegende, + Timeline: primevue.timeline, + }, + props: { + projekte: { type: Array, default: () => [] }, + notenOptions: { type: Array, default: () => [] }, + formatDateFn: { type: Function, required: true } + }, + data() { + return { + legendExpanded: false, + expandedProjects: {} + } + }, + computed: { + student() { + return this.projekte?.[0] ?? null + } + }, + watch: { + projekte: { + immediate: true, + handler(val) { + // open all projects by default whenever the student changes + const state = {} + val?.forEach(p => { state[p.projektarbeit_id] = true }) + this.expandedProjects = state + this.legendExpanded = false + } + } + }, + methods: { + getNoteBezeichnung(projektarbeit) { + if(projektarbeit.note && this.notenOptions) { + const noteOpt = this.notenOptions.find(opt => opt.note == projektarbeit.note) + return noteOpt?.bezeichnung + } else { + return '' + } + }, + getSavedTerminInfoString(termin) { + const isUpdate = termin.updateamum != null; + + const fullname = isUpdate + ? termin.updatevon_fullname + : termin.insertvon_fullname; + + const datetime = isUpdate + ? termin.updateamum + : termin.insertamum; + + return this.$p.t('ui/savedAtByV3', [formatDateTime(datetime), fullname]) + }, + getItemBezeichnung(item) { + if (!item?.bezeichnung) return '' + return item.bezeichnung?.bezeichnung ?? item.bezeichnung + }, + getItemNote(item) { + if (!item?.note) return '' + if (item.note?.bezeichnung) return item.note.bezeichnung + return this.notenOptions?.find(n => n.note == item.note)?.bezeichnung ?? String(item.note) + }, + getIconClass(dateStyle) { + return ({ + verspaetet: 'fa-solid fa-triangle-exclamation', + verpasst: 'fa-solid fa-calendar-xmark', + abzugeben: 'fa-solid fa-hourglass-half', + standard: 'fa-solid fa-clock', + abgegeben: 'fa-solid fa-paperclip', + beurteilungerforderlich: 'fa-solid fa-list-check', + bestanden: 'fa-solid fa-check', + nichtbestanden: 'fa-solid fa-circle-exclamation', + })[dateStyle] ?? '' + }, + getBetreuerLabel(projekt) { + return projekt.erstbetreuer_full_name + || (projekt.betreuer_vorname ? `${projekt.betreuer_vorname} ${projekt.betreuer_nachname}`.trim() : null) + }, + toggleProject(id) { + this.expandedProjects[id] = !this.expandedProjects[id] + } + }, + template: ` +
+ +
+ {{ student.student_vorname }} {{ student.student_nachname }} + {{ student.student_uid }} + {{ student.matrikelnr }} +
+ +
+ + + +
+ +
+ {{ projekt.betreuerart || $capitalize($p.t('abgabetool/c4erstbetreuerv2')) }}: + {{ getBetreuerLabel(projekt) }} +
+ + + + + + + + + + +
+
+ +
+ +
+ +
+
+ +
+ ` +} + +export default AbgabeStudentTimeline; \ 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 6a2d73466..1c272a501 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -7,6 +7,7 @@ import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiStudiensemester from '../../../api/factory/studiensemester.js'; import AbgabeterminStatusLegende from "./StatusLegende.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js"; +import AbgabeStudentTimeline from "./AbgabeStudentTimeline.js"; import { splitMailsHelper } from "../../../helpers/EmailHelpers.js" import { getDateStyleClass} from "./getDateStyleClass.js"; import { dateFilter } from '../../../tabulator/filters/DatesManual.js'; @@ -16,6 +17,7 @@ export const AbgabetoolAssistenz = { name: "AbgabetoolAssistenz", components: { AbgabeterminStatusLegende, + AbgabeStudentTimeline, BsModal, BsOffcanvas, CoreFilterCmpt, @@ -76,7 +78,7 @@ export const AbgabetoolAssistenz = { colLayoutRestoredFlat: false, sortRestoredFlat: false, stateRestoredFlat: false, - timelineProjekt: null, + timelineProjekte: [], selectedStudiengangOption: null, studiengaengeOptions: null, detailIsFullscreen: false, @@ -1878,21 +1880,54 @@ export const AbgabetoolAssistenz = { this.$refs.modalContainerAbgabeDetail.show() }, openTimeline(val) { - const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id) - if(!projekt) { + + this.$api.call(ApiAbgabe.fetchProjektarbeitenHistory(val.student_uid)).then(res => { + console.log(res) - this.$fhcAlert.alertInfo('Keine projektarbeit gefunden') - - return - } - projekt.abgabetermine.forEach(termin => { - // show note only on termine with abgabetypen which are benotbar - const terminTypOpt = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == termin.paabgabetyp_kurzbz) - termin.benotbar = terminTypOpt.benotbar + res.data.forEach(projekt => { + projekt.abgabetermine?.forEach(termin => { + // only set this if it has not been set yet and abgabetermin has a note (qgate) + if(!termin.noteBackend && termin.note) { + termin.noteBackend = this.notenOptions.find(opt => termin.note == opt.note) + } + + termin.dateStyle = getDateStyleClass(termin, this.notenOptions) + + const terminTypOpt = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == termin.paabgabetyp_kurzbz) + if (terminTypOpt) termin.benotbar = terminTypOpt.benotbar + }) + }) + + // keep the history API stub for future use + + + this.timelineProjekte = res.data + this.$refs.drawer.show() }) - this.timelineProjekt = projekt - this.$refs.drawer.show() }, + // openTimeline(val) { + // + // this.$api.call(ApiAbgabe.fetchProjektarbeitenHistory(val.student_uid)).then(res => { + // console.log(res) + // }) + // + // const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id) + // if(!projekt) { + // + // this.$fhcAlert.alertInfo('Keine projektarbeit gefunden') + // + // return + // } + // + // projekt.abgabetermine.forEach(termin => { + // // show note only on termine with abgabetypen which are benotbar + // const terminTypOpt = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == termin.paabgabetyp_kurzbz) + // termin.benotbar = terminTypOpt.benotbar + // }) + // + // this.timelineProjekt = projekt + // this.$refs.drawer.show() + // }, paabgabetypFormatter(cell) { const key = cell.getValue() return this.$p.t('abgabetool/c4paatyp' + key) @@ -2562,102 +2597,11 @@ export const AbgabetoolAssistenz = { {{ $p.t('abgabetool/c4projektarbeitTimelineTitle') }} -
- - - - -
- - - - - - - - - - - +
diff --git a/public/js/components/Cis/Abgabetool/dateUtils.js b/public/js/components/Cis/Abgabetool/dateUtils.js index 5723d9baf..65971cf0a 100644 --- a/public/js/components/Cis/Abgabetool/dateUtils.js +++ b/public/js/components/Cis/Abgabetool/dateUtils.js @@ -14,7 +14,7 @@ export function formatISODate(dateParam) { export function formatDateTime(dateParam) { if (!dateParam) return ''; - const date = luxon.DateTime.fromFormat(dateParam, "yyyy-MM-dd HH:mm:ss"); + const date = luxon.DateTime.fromSQL(dateParam); return date.isValid ? date.toFormat("dd.MM.yyyy HH:mm") : ''; }