wip abgabetool;

This commit is contained in:
Johann Hoffmann
2025-11-05 15:34:10 +01:00
parent 3d51753419
commit 3878fce625
13 changed files with 299 additions and 111 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ if (! defined('BASEPATH')) exit('No direct script access allowed');
// CMS Content Id for CIS4 Menu Root // CMS Content Id for CIS4 Menu Root
$config['cis_menu_root_content_id'] = 11087; $config['cis_menu_root_content_id'] = 11091;
// send Mails for ProfilUpdate // send Mails for ProfilUpdate
$config['cis_send_profil_update_mails'] = true; $config['cis_send_profil_update_mails'] = true;
// Vilesci CI BaseUrl // Vilesci CI BaseUrl
@@ -672,6 +672,7 @@ class Abgabe extends FHCAPI_Controller
$paabgabetyp_kurzbz = $_POST['paabgabetyp_kurzbz']; $paabgabetyp_kurzbz = $_POST['paabgabetyp_kurzbz'];
$bezeichnung = $_POST['bezeichnung']; $bezeichnung = $_POST['bezeichnung'];
$kurzbz = $_POST['kurzbz']; $kurzbz = $_POST['kurzbz'];
$fixtermin = $_POST['fixtermin'];
if (!isset($projektarbeit_ids) || !is_array($projektarbeit_ids) || empty($projektarbeit_ids) if (!isset($projektarbeit_ids) || !is_array($projektarbeit_ids) || empty($projektarbeit_ids)
|| !isset($datum) || isEmptyString($datum) || !isset($datum) || isEmptyString($datum)
@@ -697,13 +698,14 @@ class Abgabe extends FHCAPI_Controller
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$res = []; $res = [];
$abgaben = [];
foreach ($projektarbeit_ids as $projektarbeit_id) { foreach ($projektarbeit_ids as $projektarbeit_id) {
$result = $this->PaabgabeModel->insert( $result = $this->PaabgabeModel->insert(
array( array(
'projektarbeit_id' => $projektarbeit_id, 'projektarbeit_id' => $projektarbeit_id,
'paabgabetyp_kurzbz' => $paabgabetyp_kurzbz, 'paabgabetyp_kurzbz' => $paabgabetyp_kurzbz,
'fixtermin' => false, 'fixtermin' => $fixtermin,
'datum' => $datum, 'datum' => $datum,
'kurzbz' => $kurzbz, 'kurzbz' => $kurzbz,
'insertvon' => getAuthUID(), 'insertvon' => getAuthUID(),
@@ -711,7 +713,9 @@ class Abgabe extends FHCAPI_Controller
) )
); );
$data = $this->getDataOrTerminateWithError($result); $dataAbgabe = $this->getDataOrTerminateWithError($result);
$abgaben[]= getData($this->PaabgabeModel->load($dataAbgabe))[0];
// $res[] = $data; // $res[] = $data;
@@ -751,7 +755,7 @@ class Abgabe extends FHCAPI_Controller
$this->logLib->logInfoDB(array('serientermin angelegt',$res, getAuthUID(), getAuthPersonId())); $this->logLib->logInfoDB(array('serientermin angelegt',$res, getAuthUID(), getAuthPersonId()));
$this->terminateWithSuccess($res); $this->terminateWithSuccess(array($res, $abgaben));
} }
@@ -865,9 +869,6 @@ class Abgabe extends FHCAPI_Controller
if (!isset($studiengang_kz) || isEmptyString($studiengang_kz)) if (!isset($studiengang_kz) || isEmptyString($studiengang_kz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
// TODO revert arr return new/old
// $arr = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz);
// $result = $arr[0];
$result = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz); $result = $this->ProjektarbeitModel->getProjektarbeitenForStudiengang($studiengang_kz);
$projektarbeiten = $this->getDataOrTerminateWithError($result); $projektarbeiten = $this->getDataOrTerminateWithError($result);
@@ -904,7 +905,7 @@ class Abgabe extends FHCAPI_Controller
$stg_allowed = $this->permissionlib->getSTG_isEntitledFor('basis/abgabe_assistenz:rw'); $stg_allowed = $this->permissionlib->getSTG_isEntitledFor('basis/abgabe_assistenz:rw');
if($stg_allowed == false) { if($stg_allowed == false) {
$this->terminateWithError($this->p->t('global', 'keineBerechtigung'), 'general'); $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general');
} }
$this->load->model('organisation/Studiengang_model', 'StudiengangModel'); $this->load->model('organisation/Studiengang_model', 'StudiengangModel');
@@ -407,8 +407,6 @@ class Projektarbeit_model extends DB_Model
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON (betreuer_person.person_id = betreuer_benutzer.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') WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv AND (lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbegutachter' OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Begutachter') AND student_benutzer.aktiv AND (lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbegutachter' OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Begutachter')
-- AND lehre.tbl_projektarbeit.note IS NULL
-- AND public.tbl_studiengang.studiengang_kz= 257
AND public.tbl_studiengang.studiengang_kz = ? AND public.tbl_studiengang.studiengang_kz = ?
ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
) as tmp"; ) as tmp";
+1
View File
@@ -25,6 +25,7 @@ $includesArray = array(
'vendor/npm-asset/primevue/speeddial/speeddial.min.js', 'vendor/npm-asset/primevue/speeddial/speeddial.min.js',
'vendor/npm-asset/primevue/textarea/textarea.min.js', 'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/npm-asset/primevue/timeline/timeline.min.js', 'vendor/npm-asset/primevue/timeline/timeline.min.js',
'vendor/npm-asset/primevue/inplace/inplace.min.js',
'vendor/moment/luxonjs/luxon.min.js' 'vendor/moment/luxonjs/luxon.min.js'
), ),
'customJSModules' => array( 'customJSModules' => array(
@@ -33,6 +33,7 @@ $includesArray = array(
'vendor/npm-asset/primevue/speeddial/speeddial.min.js', 'vendor/npm-asset/primevue/speeddial/speeddial.min.js',
'vendor/npm-asset/primevue/textarea/textarea.min.js', 'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/npm-asset/primevue/timeline/timeline.min.js', 'vendor/npm-asset/primevue/timeline/timeline.min.js',
'vendor/npm-asset/primevue/inplace/inplace.min.js',
'vendor/moment/luxonjs/luxon.min.js' 'vendor/moment/luxonjs/luxon.min.js'
), ),
'customJSModules' => array( 'customJSModules' => array(
+2 -2
View File
@@ -73,11 +73,11 @@ export default {
params: { paabgabe_id } params: { paabgabe_id }
}; };
}, },
postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids) { postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids, fixtermin) {
return { return {
method: 'post', method: 'post',
url: '/api/frontend/v1/Abgabe/postSerientermin', url: '/api/frontend/v1/Abgabe/postSerientermin',
params: { datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids } params: { datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids, fixtermin }
}; };
}, },
fetchDeadlines(person_id) { fetchDeadlines(person_id) {
@@ -86,11 +86,17 @@ export const AbgabeMitarbeiterDetail = {
} }
if(newTerminRes.note) newTerminRes.note = noteOpt if(newTerminRes.note) newTerminRes.note = noteOpt
const existingTerminRes = res.data[1] const existingTerminRes = res.data[1]
const abgabeOpt = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == newTerminRes.paabgabetyp_kurzbz)
newTerminRes.bezeichnung = { newTerminRes.bezeichnung = {
bezeichnung: termin.bezeichnung?.bezeichnung, bezeichnung: termin.bezeichnung?.bezeichnung,
paabgabetyp_kurzbz: termin.bezeichnung?.paabgabetyp_kurzbz paabgabetyp_kurzbz: termin.bezeichnung?.paabgabetyp_kurzbz,
benotbar: abgabeOpt.benotbar
} }
// only insert new abgabe if we actually created a new one, not when saving/editing existing // only insert new abgabe if we actually created a new one, not when saving/editing existing
if(!existingTerminRes){ if(!existingTerminRes){
this.projektarbeit.abgabetermine.push(newTerminRes) this.projektarbeit.abgabetermine.push(newTerminRes)
@@ -711,8 +717,8 @@ export const AbgabeMitarbeiterDetail = {
<div v-if="projektarbeit?.abgabetermine.length == 0" style="display:flex; justify-content: center; align-content: center;"> <div v-if="projektarbeit?.abgabetermine.length == 0" style="display:flex; justify-content: center; align-content: center;">
<h3>{{ $capitalize( $p.t('abgabetool/c4keineAbgabetermineGefunden') )}}</h3> <h3>{{ $capitalize( $p.t('abgabetool/c4keineAbgabetermineGefunden') )}}</h3>
</div> </div>
</div>
</div> </div>
`, `,
}; };
@@ -7,8 +7,14 @@ import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiAbgabe from '../../../api/factory/abgabe.js'
import AbgabeterminStatusLegende from "./StatusLegende.js"; import AbgabeterminStatusLegende from "./StatusLegende.js";
const todayISO = '2025-08-08' // spoofed date testing
const today = new Date(todayISO) // const todayISO = '2025-08-08'
// const today = new Date(todayISO)
// const now = luxon.DateTime.fromISO(todayISO)
// prod code
const today = new Date()
const now = luxon.DateTime.now()
export const AbgabetoolAssistenz = { export const AbgabetoolAssistenz = {
name: "AbgabetoolAssistenz", name: "AbgabetoolAssistenz",
@@ -21,6 +27,7 @@ export const AbgabetoolAssistenz = {
VerticalSplit, VerticalSplit,
Checkbox: primevue.checkbox, Checkbox: primevue.checkbox,
Dropdown: primevue.dropdown, Dropdown: primevue.dropdown,
Inplace: primevue.inplace,
Textarea: primevue.textarea, Textarea: primevue.textarea,
Timeline: primevue.timeline, Timeline: primevue.timeline,
VueDatePicker VueDatePicker
@@ -45,6 +52,8 @@ export const AbgabetoolAssistenz = {
}, },
data() { data() {
return { return {
notenOptionFilter: null,
inplaceToggle: false,
headerFiltersRestored: false, headerFiltersRestored: false,
filtersRestored: false, filtersRestored: false,
colLayoutRestored: false, colLayoutRestored: false,
@@ -54,7 +63,6 @@ export const AbgabetoolAssistenz = {
selectedStudiengangOption: null, selectedStudiengangOption: null,
studiengaengeOptions: null, studiengaengeOptions: null,
detailIsFullscreen: false, detailIsFullscreen: false,
showZweitbetreuerCol: false,
phrasenPromise: null, phrasenPromise: null,
phrasenResolved: false, phrasenResolved: false,
turnitin_link: null, turnitin_link: null,
@@ -63,6 +71,7 @@ export const AbgabetoolAssistenz = {
loading: false, loading: false,
abgabeTypeOptions: null, abgabeTypeOptions: null,
notenOptions: null, notenOptions: null,
allowedNotenFilterOptions: null,
allowedNotenOptions: null, allowedNotenOptions: null,
serienTermin: Vue.reactive({ serienTermin: Vue.reactive({
datum: new Date(), datum: new Date(),
@@ -70,7 +79,8 @@ export const AbgabetoolAssistenz = {
paabgabetyp_kurzbz: 'zwischen', paabgabetyp_kurzbz: 'zwischen',
bezeichnung: 'Zwischenabgabe' bezeichnung: 'Zwischenabgabe'
}, },
kurzbz: '' kurzbz: '',
fixtermin: false
}), }),
showAll: false, showAll: false,
tabulatorUuid: Vue.ref(0), tabulatorUuid: Vue.ref(0),
@@ -91,6 +101,7 @@ export const AbgabetoolAssistenz = {
selectable: true, selectable: true,
selectableCheck: this.selectionCheck, selectableCheck: this.selectionCheck,
rowHeight: 40, rowHeight: 40,
renderVerticalBuffer: 2000,
responsiveLayout: true, responsiveLayout: true,
columns: [ columns: [
{ {
@@ -106,6 +117,7 @@ export const AbgabetoolAssistenz = {
width: 40 width: 40
}, },
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, tooltip:false, minWidth: 150,}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, tooltip:false, minWidth: 150,},
{title: 'pa_id', field: 'projektarbeit_id', visible: true},
// {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1,responsive:0, tooltip: false}, // {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1,responsive:0, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter,responsive:0, widthGrow: 1, tooltip: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter,responsive:0, widthGrow: 1, tooltip: false},
// {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4termineTimeLine'))), headerFilter: true, field: 'abgabetermine',responsive:2, formatter: this.timelineFormatter, widthGrow: 1, tooltip: false}, // {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4termineTimeLine'))), headerFilter: true, field: 'abgabetermine',responsive:2, formatter: this.timelineFormatter, widthGrow: 1, tooltip: false},
@@ -114,10 +126,11 @@ export const AbgabetoolAssistenz = {
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true,responsive:2, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true,responsive:2, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))), field: 'note', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, visible: false, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, visible: false, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, responsive:3, visible: false, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuer'))), field: 'erstbetreuer', headerFilter: true, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuer'))), field: 'erstbetreuer', headerFilter: true, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuer'))), field: 'zweitbetreuer', headerFilter: true, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1, visible: Vue.computed(()=>{return this.showZweitbetreuerCol})}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuer'))), field: 'zweitbetreuer', headerFilter: true, responsive:3,formatter: this.centeredTextFormatter, widthGrow: 1, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))), headerFilter: true, field: 'prevTermin', responsive:4, formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))), headerFilter: true, field: 'prevTermin', responsive:4, formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), headerFilter: true, field: 'nextTermin', responsive:4, formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), headerFilter: true, field: 'nextTermin', responsive:4, formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
], ],
@@ -153,6 +166,31 @@ export const AbgabetoolAssistenz = {
]}; ]};
}, },
methods: { methods: {
checkAbgabetermineProjektarbeit(projekt) {
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
// while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = this.getDateStyleClass(termin)
const date = luxon.DateTime.fromISO(termin.datum)
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
if (termin.diffMs < 0) {
if (!projekt.prevTermin ||
termin.diffMs > projekt.prevTermin.diffMs // larger (less negative) = closer to now
) {
projekt.prevTermin = termin;
}
} else if (termin.diffMs > 0) {
if (!projekt.nextTermin ||
termin.diffMs < projekt.nextTermin.diffMs // smaller positive = closer to now
) {
projekt.nextTermin = termin;
}
}
})
},
loadState() { loadState() {
return JSON.parse(localStorage.getItem(this.abgabeTableOptions.persistenceID) || "null"); return JSON.parse(localStorage.getItem(this.abgabeTableOptions.persistenceID) || "null");
}, },
@@ -214,7 +252,7 @@ export const AbgabetoolAssistenz = {
table.on("renderComplete", () => { table.on("renderComplete", () => {
if(!this.stateRestored) { if(!this.stateRestored) {
debugger
if (saved?.columns && !this.colLayoutRestored) { if (saved?.columns && !this.colLayoutRestored) {
const layout = saved.columns.map(col => ({ const layout = saved.columns.map(col => ({
field: col.field, field: col.field,
@@ -222,10 +260,13 @@ export const AbgabetoolAssistenz = {
visible: col.visible, visible: col.visible,
// add more if needed, but keep it simple // add more if needed, but keep it simple
})); }));
console.log(layout)
table.setColumnLayout(layout); const safeLayout = layout.filter(col =>
col.field && !["rowSelection", "rowHandle", "rowNum", "zweitbetreuer"].includes(col.field)
);
table.setColumnLayout(safeLayout);
this.colLayoutRestored = true; this.colLayoutRestored = true;
} }
@@ -270,6 +311,9 @@ export const AbgabetoolAssistenz = {
getOptionLabelStg(option){ getOptionLabelStg(option){
return option.kurzbzlang + ' ' + option.bezeichnung return option.kurzbzlang + ' ' + option.bezeichnung
}, },
getNotenFilterOptionLabel(option) {
return option.bezeichnung
},
sgChanged(e) { sgChanged(e) {
debugger debugger
}, },
@@ -304,6 +348,7 @@ export const AbgabetoolAssistenz = {
btn.style.height = "100%"; // fill parent container height btn.style.height = "100%"; // fill parent container height
btn.style.aspectRatio = "1 / 1"; // keep square shape (optional) btn.style.aspectRatio = "1 / 1"; // keep square shape (optional)
btn.style.padding = "0"; // remove extra padding for compactness 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.innerHTML = `<i class="${iconClass}" style="color:#00649C; font-size:1.1rem;"></i>`;
btn.title = this.$capitalize(this.$p.t(titleKey)); btn.title = this.$capitalize(this.$p.t(titleKey));
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
@@ -354,27 +399,88 @@ export const AbgabetoolAssistenz = {
this.$refs.modalContainerAddSeries.show() this.$refs.modalContainerAddSeries.show()
}, },
addSeries() { addSeries() {
const pids = this.selectedData?.map(projekt => projekt.projektarbeit_id)
this.saving = true this.saving = true
this.$api.call(ApiAbgabe.postSerientermin( this.$api.call(ApiAbgabe.postSerientermin(
this.serienTermin.datum.toISOString(), this.serienTermin.datum.toISOString(),
this.serienTermin.bezeichnung.paabgabetyp_kurzbz, this.serienTermin.bezeichnung.paabgabetyp_kurzbz,
this.serienTermin.bezeichnung.bezeichnung, this.serienTermin.bezeichnung.bezeichnung,
this.serienTermin.kurzbz, this.serienTermin.kurzbz,
this.selectedData?.map(projekt => projekt.projektarbeit_id) pids,
this.serienTermin.fixtermin
)).then(res => { )).then(res => {
// TODO
// sticky lifetime somehow
if (res.meta.status === "success" && res.data) { if (res.meta.status === "success" && res.data) {
this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/serienTerminGespeichert')) this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/serienTerminGespeichert'))
// TODO: sticky lifetime erhöhen um sinnvoll lesen zu können? // TODO: sticky lifetime erhöhen um sinnvoll lesen zu können?
this.$fhcAlert.alertInfo(this.$p.t('abgabetool/serienTerminEmailSentInfo', [this.createInfoString(res.data)])); this.$fhcAlert.alertInfo(this.$p.t('abgabetool/serienTerminEmailSentInfo', [this.createInfoString(res.data[0])]));
} else { } else {
this.$fhcAlert.alertError(this.$p.t('abgabetool/errorSerienterminSpeichern')) this.$fhcAlert.alertError(this.$p.t('abgabetool/errorSerienterminSpeichern'))
} }
// put new abgaben into projektarbeiten
const newAbgaben = res.data[1]
pids.forEach(pid => {
const abgabe = newAbgaben.find(abgabe => abgabe.projektarbeit_id == pid)
const pa = this.projektarbeiten.find(pa => pa.projektarbeit_id == pid)
abgabe.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == abgabe.paabgabetyp_kurzbz)
pa.abgabetermine.push(abgabe)
pa.abgabetermine.sort((a, b) => new Date(a.datum) - new Date(b.datum))
})
// reset selection to empty
this.$refs.abgabeTable.tabulator.deselectRow()
const mappedData = this.mapProjekteToTableData(this.projektarbeiten)
console.log('this.projektarbeiten', this.projektarbeiten)
console.log('mappedData', mappedData)
this.$refs.abgabeTable.tabulator.clearData()
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.$refs.abgabeTable.tabulator.replaceData(mappedData)
// this.$refs.abgabeTable.tabulator.redraw(true)
}).finally(()=>{ }).finally(()=>{
this.saving = false this.saving = false
}) })
this.$refs.modalContainerAddSeries.hide() this.$refs.modalContainerAddSeries.hide()
}, },
mapProjekteToTableData(projekte) {
// const now = luxon.DateTime.now();
return projekte.map(projekt => {
projekt.prevTermin = null;
projekt.nextTermin = null;
this.checkAbgabetermineProjektarbeit(projekt)
return {
...projekt,
abgabetermine: projekt.abgabetermine,
details: {
student_uid: projekt.student_uid,
projektarbeit_id: projekt.projektarbeit_id,
},
pkz: this.buildPKZ(projekt),
beurteilung: projekt.beurteilungLink ?? null,
sem: projekt.studiensemester_kurzbz,
stg: this.buildStg(projekt),
mail: this.buildMailToLink(projekt),
erstbetreuer: this.buildErstbetreuer(projekt),
zweitbetreuer: this.buildZweitbetreuer(projekt),
typ: projekt.projekttyp_kurzbz,
titel: projekt.titel
}
})
},
createInfoString(data) { createInfoString(data) {
let str = ''; let str = '';
@@ -390,7 +496,7 @@ export const AbgabetoolAssistenz = {
}, },
setDetailComponent(details){ setDetailComponent(details){
const pa = this.projektarbeiten.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id) const pa = this.projektarbeiten.find(projektarbeit => projektarbeit.projektarbeit_id == details.projektarbeit_id)
// pa.isCurrent = res.data[1] // pa.isCurrent = res.data[1]
@@ -445,7 +551,7 @@ export const AbgabetoolAssistenz = {
const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id) const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id)
if(!projekt) { if(!projekt) {
this.$fhcAlert.alertInfo('keine projektarbeit gefunden') this.$fhcAlert.alertInfo('Keine projektarbeit gefunden')
return return
} }
@@ -455,7 +561,6 @@ export const AbgabetoolAssistenz = {
termin.benotbar = terminTypOpt.benotbar termin.benotbar = terminTypOpt.benotbar
}) })
this.timelineProjekt = projekt this.timelineProjekt = projekt
// this.timelineAbgabetermine = projekt.abgabetermine
this.$refs.drawer.show() this.$refs.drawer.show()
}, },
centeredTextFormatter(cell) { centeredTextFormatter(cell) {
@@ -503,7 +608,7 @@ export const AbgabetoolAssistenz = {
case 'verpasst': case 'verpasst':
icon = '<i class="fa-solid fa-calendar-xmark"></i>' icon = '<i class="fa-solid fa-calendar-xmark"></i>'
break break
case 'verpasst': case 'abzugeben':
icon = '<i class="fa-solid fa-hourglass-half"></i>' icon = '<i class="fa-solid fa-hourglass-half"></i>'
break break
case 'standard': case 'standard':
@@ -514,12 +619,14 @@ export const AbgabetoolAssistenz = {
break break
} }
const bezeichnung = val.bezeichnung?.bezeichnung ?? val.bezeichnung
return '<div style="display: flex; height: 100%">' + 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;">' + '<div class=' + val.dateStyle + "-header" + ' style="width:48px; height: 100%; padding: 0px; display: flex; align-items: center; justify-content: center;">' +
icon + icon +
'</div>' + '</div>' +
'<div style="margin-left: 4px;">' + '<div style="margin-left: 4px;">' +
'<p style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+val.bezeichnung+' - '+ this.formatDate(val.datum)+'</p>' + '<p style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+bezeichnung+' - '+ this.formatDate(val.datum)+'</p>' +
'</div>'+ '</div>'+
'</div>' '</div>'
@@ -550,64 +657,12 @@ export const AbgabetoolAssistenz = {
setupData(data){ setupData(data){
this.projektarbeiten = data[0] this.projektarbeiten = data[0]
this.domain = data[1] this.domain = data[1]
const now = luxon.DateTime.fromISO(todayISO) const mappedData = this.mapProjekteToTableData(this.projektarbeiten)
// const now = luxon.DateTime.now();
const d = data[0].map(projekt => {
let mode = 'detailTermine'
projekt.prevTermin = undefined;
projekt.nextTermin = undefined;
// only show 2tbetreuer col if any projektarbeit has one
if(projekt.zweitbetreuer_full_name) this.showZweitbetreuerCol = true
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
// while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = this.getDateStyleClass(termin)
const date = luxon.DateTime.fromISO(termin.datum)
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
if (termin.diffMs < 0) {
if (!projekt.prevTermin ||
termin.diffMs > projekt.prevTermin.diffMs // larger (less negative) = closer to now
) {
projekt.prevTermin = termin;
}
} else if (termin.diffMs > 0) {
if (!projekt.nextTermin ||
termin.diffMs < projekt.nextTermin.diffMs // smaller positive = closer to now
) {
projekt.nextTermin = termin;
}
}
})
return {
...projekt,
abgabetermine: projekt.abgabetermine,
details: {
student_uid: projekt.student_uid,
projektarbeit_id: projekt.projektarbeit_id,
},
pkz: this.buildPKZ(projekt),
beurteilung: projekt.beurteilungLink ?? null,
sem: projekt.studiensemester_kurzbz,
stg: this.buildStg(projekt),
mail: this.buildMailToLink(projekt),
erstbetreuer: this.buildErstbetreuer(projekt),
zweitbetreuer: this.buildZweitbetreuer(projekt),
typ: projekt.projekttyp_kurzbz,
titel: projekt.titel
}
})
this.$refs.abgabeTable.tabulator.clearData() this.$refs.abgabeTable.tabulator.clearData()
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns) this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.$refs.abgabeTable.tabulator.setData(d); this.$refs.abgabeTable.tabulator.setData(mappedData);
}, },
loadProjektarbeiten(all = false, callback) { loadProjektarbeiten(all = false, callback) {
this.loading = true this.loading = true
@@ -680,14 +735,12 @@ export const AbgabetoolAssistenz = {
this.turnitin_link = res.data?.turnitin_link this.turnitin_link = res.data?.turnitin_link
this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link
}).catch(e => { }).catch(e => {
console.log(e)
this.loading = false this.loading = false
}) })
// fetch studiengänge options // fetch studiengänge options
this.$api.call(ApiAbgabe.getStudiengaenge()).then(res => { this.$api.call(ApiAbgabe.getStudiengaenge()).then(res => {
this.studiengaengeOptions = res.data this.studiengaengeOptions = res.data
console.log(this.studiengaengeOptions)
}).catch(e => { }).catch(e => {
this.loading = false this.loading = false
}) })
@@ -701,6 +754,26 @@ export const AbgabetoolAssistenz = {
opt => opt.bezeichnung === 'Bestanden' opt => opt.bezeichnung === 'Bestanden'
|| opt.bezeichnung === 'Nicht bestanden' || opt.bezeichnung === 'Nicht bestanden'
) )
// allowedNotenOptions apply to quality gates abgabetermine
// this selection is about graded projektarbeiten, so take different options here
this.allowedNotenFilterOptions = [
{
bezeichnung: this.$p.t('abgabetool/keineNoteEingetragen'),
note: null,
},
{
bezeichnung: this.$p.t('abgabetool/c4benotet'),
note: 1,
},
{
bezeichnung: Vue.computed(this.$p.t('abgabetool/showAll')),
note: -1,
},
]
this.notenOptionFilter = this.allowedNotenFilterOptions[0]
}).catch(e => { }).catch(e => {
this.loading = false this.loading = false
}) })
@@ -734,7 +807,7 @@ export const AbgabetoolAssistenz = {
{{$p.t('abgabetool/c4fixterminv2')}} {{$p.t('abgabetool/c4fixterminv2')}}
</div> </div>
<div class="col-3 d-flex justify-content-center align-items-center"> <div class="col-3 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4zieldatum')}} {{$capitalize($p.t('abgabetool/c4zieldatum'))}}
</div> </div>
<div class="col-3 d-flex justify-content-center align-items-center"> <div class="col-3 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4abgabetypv2')}} {{$p.t('abgabetool/c4abgabetypv2')}}
@@ -802,19 +875,53 @@ export const AbgabetoolAssistenz = {
ref="drawer" ref="drawer"
placement="end" placement="end"
:backdrop="true" :backdrop="true"
@shownBsOffcanvas="onShown"
@hiddenBsOffcanvas="onHidden"
:style="{ '--bs-offcanvas-width': '600px' }" :style="{ '--bs-offcanvas-width': '600px' }"
> >
<template #title> <template #title>
{{ $p.t('abgabetool/c4projektarbeitTimelineTitle') }} {{ $p.t('abgabetool/c4projektarbeitTimelineTitle') }}
</template> </template>
<!-- TODO: take care of absolute offset values -->
<div class="row">
<Inplace
closable
:closeButtonProps="{
style: {
position: 'absolute',
top: '80px',
right: '80px',
zIndex: 1
}
}"
>
<template #display> {{ $capitalize($p.t('abgabetool/showStudentDetails'))}} </template>
<template #content>
<div class="col-auto">
<div class="row">
<div class="col-2">Student: </div>
<div class="col-7">{{timelineProjekt?.student_vorname}} {{timelineProjekt?.student_nachname}}</div>
</div>
<div class="row">
<div class="col-2">Uid: </div>
<div class="col-7">{{timelineProjekt?.student_uid}}</div>
</div>
<div class="row">
<div class="col-2">{{timelineProjekt?.betreuerart}}: </div>
<div class="col-7">{{timelineProjekt?.erstbetreuer_full_name}}</div>
</div>
<div class="row">
<div class="col-2">Titel: </div>
<div class="col-7">{{timelineProjekt?.titel}}</div>
</div>
</div>
</template>
</Inplace>
</div>
<Timeline <Timeline
:value="timelineProjekt?.abgabetermine" :value="timelineProjekt?.abgabetermine"
align="right" align="right"
> >
<template #marker="slotProps"> <template #marker="slotProps">
<div :class="slotProps.item.dateStyle + '-header'" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;"> <div :class="slotProps.item.dateStyle + '-header'" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i v-if="slotProps.item.dateStyle == 'verspaetet'" class="fa-solid fa-triangle-exclamation"></i> <i v-if="slotProps.item.dateStyle == 'verspaetet'" class="fa-solid fa-triangle-exclamation"></i>
@@ -873,7 +980,7 @@ export const AbgabetoolAssistenz = {
<div style="max-height:40vw;"> <div style="max-height:40vw;">
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2> <h2 tabindex="1">{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
</div> </div>
<div class="col-3"> <div class="col-3">
<Dropdown <Dropdown
@@ -884,12 +991,29 @@ export const AbgabetoolAssistenz = {
v-model="selectedStudiengangOption" v-model="selectedStudiengangOption"
:options="studiengaengeOptions" :options="studiengaengeOptions"
showClear showClear
:tabindex="2"
> >
<template #optionsgroup="slotProps"> <template #optionsgroup="slotProps">
<div> {{ option.kurzbzlang }} {{ option.bezeichnung }} </div> <div> {{ option.kurzbzlang }} {{ option.bezeichnung }} </div>
</template> </template>
</Dropdown> </Dropdown>
</div> </div>
<div class="col-3">
<Dropdown
@change="sgChanged"
:placeholder="$p.t('lehre/note')"
:style="{'width': '100%', 'scroll-behavior': 'auto !important'}"
:optionLabel="getNotenFilterOptionLabel"
v-model="notenOptionFilter"
:options="allowedNotenFilterOptions"
showClear
:tabindex="2"
>
<template #optionsgroup="slotProps">
<div>{{ option.bezeichnung }} </div>
</template>
</Dropdown>
</div>
</div> </div>
<hr> <hr>
<core-filter-cmpt <core-filter-cmpt
@@ -908,14 +1032,7 @@ export const AbgabetoolAssistenz = {
:useSelectionSpan="false" :useSelectionSpan="false"
> >
<template #actions> <template #actions>
<button @click="sendEmailStudierende" role="button" class="btn btn-secondary ml-2">
{{ $p.t('abgabetool/c4sendEmailStudierende') }}
</button>
<button @click="sendEmailBegutachter" role="button" class="btn btn-secondary ml-2">
{{ $p.t('abgabetool/c4sendEmailBetreuer') }}
</button>
</template> </template>
</core-filter-cmpt> </core-filter-cmpt>
</div> </div>
@@ -179,7 +179,8 @@ export const AbgabetoolMitarbeiter = {
this.serienTermin.bezeichnung.paabgabetyp_kurzbz, this.serienTermin.bezeichnung.paabgabetyp_kurzbz,
this.serienTermin.bezeichnung.bezeichnung, this.serienTermin.bezeichnung.bezeichnung,
this.serienTermin.kurzbz, this.serienTermin.kurzbz,
this.selectedData?.map(projekt => projekt.projektarbeit_id) this.selectedData?.map(projekt => projekt.projektarbeit_id),
false
)).then(res => { )).then(res => {
if (res.meta.status === "success" && res.data) { if (res.meta.status === "success" && res.data) {
this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/serienTerminGespeichert')) this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/serienTerminGespeichert'))
@@ -5,7 +5,7 @@ export const AbgabeterminStatusLegende = {
<div class="col" style="width: 50%; margin-left: 12px;"> <div class="col" style="width: 50%; margin-left: 12px;">
<div class="row" style="margin-bottom: 2px"> <div class="row" style="margin-bottom: 2px">
<div class="col-auto verspaetet-header" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;"> <div class="col-auto verspaetet-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-triangle-exclamation"></i> <i class="fa-solid fa-triangle-exclamation"></i>
</div> </div>
<div class="col-auto" style="display: flex; align-items: center;"> <div class="col-auto" style="display: flex; align-items: center;">
@@ -14,7 +14,7 @@ export const AbgabeterminStatusLegende = {
</div> </div>
<div class="row" style="margin-bottom: 2px"> <div class="row" style="margin-bottom: 2px">
<div class="col-auto verpasst-header" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;"> <div class="col-auto verpasst-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-calendar-xmark"></i> <i class="fa-solid fa-calendar-xmark"></i>
</div> </div>
<div class="col-auto" style="display: flex; align-items: center;"> <div class="col-auto" style="display: flex; align-items: center;">
@@ -23,7 +23,7 @@ export const AbgabeterminStatusLegende = {
</div> </div>
<div class="row" style="margin-bottom: 2px"> <div class="row" style="margin-bottom: 2px">
<div class="col-auto abzugeben-header" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;"> <div class="col-auto abzugeben-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-hourglass-half"></i> <i class="fa-solid fa-hourglass-half"></i>
</div> </div>
<div class="col-auto" style="display: flex; align-items: center;"> <div class="col-auto" style="display: flex; align-items: center;">
@@ -32,7 +32,7 @@ export const AbgabeterminStatusLegende = {
</div> </div>
<div class="row" style="margin-bottom: 2px"> <div class="row" style="margin-bottom: 2px">
<div class="col-auto standard-header" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;"> <div class="col-auto standard-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-clock"></i> <i class="fa-solid fa-clock"></i>
</div> </div>
<div class="col-auto" style="display: flex; align-items: center;"> <div class="col-auto" style="display: flex; align-items: center;">
@@ -41,7 +41,7 @@ export const AbgabeterminStatusLegende = {
</div> </div>
<div class="row" style="margin-bottom: 2px"> <div class="row" style="margin-bottom: 2px">
<div class="col-auto abgegeben-header" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;"> <div class="col-auto abgegeben-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-check"></i> <i class="fa-solid fa-check"></i>
</div> </div>
<div class="col-auto" style="display: flex; align-items: center;"> <div class="col-auto" style="display: flex; align-items: center;">
+2
View File
@@ -143,6 +143,8 @@ export default {
}, },
}, },
async created() { async created() {
debugger
this.widget = await CachedWidgetLoader.loadWidget(this.id); this.widget = await CachedWidgetLoader.loadWidget(this.id);
let component = (await import("../" + this.widget.setup.file)).default; let component = (await import("../" + this.widget.setup.file)).default;
this.$options.components["widget" + this.widget.widget_id] = component; this.$options.components["widget" + this.widget.widget_id] = component;
+2 -1
View File
@@ -380,7 +380,8 @@ export default {
} }
return result; return result;
} }
const fhcApiAxios = axios.create({ const fhcApiAxios = axios.create({
timeout: 500000, timeout: 500000,
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root
+60
View File
@@ -43173,6 +43173,26 @@ array(
) )
) )
), ),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4benotet',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Benotet",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Graded',
'description' => '',
'insertvon' => 'system'
)
)
),
array( array(
'app' => 'core', 'app' => 'core',
'category' => 'abgabetool', 'category' => 'abgabetool',
@@ -43768,6 +43788,46 @@ array(
) )
) )
), ),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'showStudentDetails',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Studenten Details anzeigen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'show student details',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'keineNoteEingetragen',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Keine Note eingetragen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Not graded yet',
'description' => '',
'insertvon' => 'system'
)
)
),
// ABGABETOOL PHRASEN END // ABGABETOOL PHRASEN END
array( array(
'app' => 'core', 'app' => 'core',