match abgabetool_detail student/mitarbeiter look&feel; WIP terminserie, zweitbetreuer logic etc

This commit is contained in:
Johann Hoffmann
2025-04-25 14:30:12 +02:00
parent 54e8a855f4
commit a5ab2f1381
9 changed files with 196 additions and 53 deletions
@@ -130,13 +130,26 @@ class Lehre extends FHCAPI_Controller
public function getStudentProjektabgaben() {
$projektarbeit_id = $this->input->get("projektarbeit_id",TRUE);
// TODO: error messages
if (!isset($projektarbeit_id) || isEmptyString($projektarbeit_id))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$projektarbeit_obj = new projektarbeit();
if($projektarbeit_id==-1)
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
if(!$projektarbeit_obj->load($projektarbeit_id))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$paIsCurrent = $projektarbeit_obj->projektarbeitIsCurrent($projektarbeit_id);
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$ret = $this->ProjektarbeitModel->getProjektarbeitAbgabetermine($projektarbeit_id);
$this->terminateWithSuccess($ret);
// TODO: fetch zweitbetreuer
$this->terminateWithSuccess(array($ret, $paIsCurrent));
}
/**
@@ -100,8 +100,8 @@ class Ort extends FHCAPI_Controller
)
";
$params = array_merge($params, [$datum, $vonStunde, $bisStunde, $datum, $vonStunde, $bisStunde]);
$this->addMeta('qry', $qry);
$this->addMeta('params', $params);
// $this->addMeta('qry', $qry);
// $this->addMeta('params', $params);
$result = $this->OrtModel->execReadOnlyQuery($qry, $params);
$this->terminateWithSuccess($result);
@@ -93,7 +93,6 @@ export const AbgabeMitarbeiterDetail = {
})
},
deleteTermin(termin) {
debugger
this.$fhcApi.factory.lehre.deleteProjektarbeitAbgabe(termin.paabgabe_id).then( (res) => {
if(res?.meta?.status == 'success') {
this.$fhcAlert.alertSuccess(this.$p.t('ui/genericDeleted', [this.$p.t('abgabetool/abgabe')]))
@@ -144,7 +143,7 @@ export const AbgabeMitarbeiterDetail = {
color = 'green'
}
return 'font-color: ' + fontColor + '; background-color: ' + color
return `font-color: ${fontColor} ; background-color: ${color}; border-radius: 50%;`
},
openBeurteilungLink(link) {
window.open(link, '_blank')
@@ -167,7 +166,14 @@ export const AbgabeMitarbeiterDetail = {
return `${day}.${month}.${year}`;
},
openStudentPage() {
// TODO: do it
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router
+ '/Cis/Abgabetool/Student/' + this.projektarbeit?.student_uid
window.open(link, '_blank')
},
openPlagiatcheck() {
// todo: hardcoded turnitin link pfui?
const link = "https://technikum-wien.turnitin.com/sso/sp/redwood/saml/5IyfmBr2OcSIaWQTKlFCGj/start"
window.open(link, '_blank')
}
},
watch: {
@@ -188,7 +194,14 @@ export const AbgabeMitarbeiterDetail = {
},
getEnduploadErlaubt() {
return !this.eidAkzeptiert
},
getSemesterBenotbar(){
return this.projektarbeit?.isCurrent ?? false
},
endUploadVorhanden(){
return this.projektarbeit?.abgabetermine.find(abgabe => abgabe.paabgabetyp_kurzbz === 'end' && abgabe.abgabedatum !== null)
}
},
created() {
@@ -206,10 +219,20 @@ export const AbgabeMitarbeiterDetail = {
<p> {{projektarbeit?.titel}}</p>
<p v-if="projektarbeit?.zweitbegutachter"> {{projektarbeit?.zweitbegutachter}}</p>
</div>
<div>
<button class="btn btn-primary border-0" @click="openStudentPage">
Speichern
<i style="margin-left: 8px" class="fa-solid fa-floppy-disk"></i>
<div class="col-4 d-flex justify-content-end">
<div>
<button :disabled="!getSemesterBenotbar && endUploadVorhanden" class="btn btn-secondary border-0" @click="openPlagiatcheck" style="margin-right: 4px;">
benoten
<i style="margin-left: 8px" class="fa-solid fa-user-check"></i>
</button>
</div>
<button v-if="projektarbeit?.betreuerart_kurzbz !== 'Zweitbegutachter'" class="btn btn-secondary border-0" @click="openPlagiatcheck" style="margin-right: 4px;">
zur Plagiatsprüfung
<i style="margin-left: 8px" class="fa-solid fa-user-check"></i>
</button>
<button class="btn btn-secondary border-0" @click="openStudentPage">
Studentenansicht
<i style="margin-left: 8px" class="fa-solid fa-eye"></i>
</button>
</div>
</div>
@@ -232,6 +255,7 @@ export const AbgabeMitarbeiterDetail = {
<div class="col-2 d-flex justify-content-center align-items-center">
<div :style="getDateStyle(termin)">
<VueDatePicker
style="width: 95%;"
v-model="termin.datum"
:clearable="false"
:disabled="!termin.allowedToSave"
@@ -10,7 +10,8 @@ export const AbgabeStudentDetail = {
InputNumber: primevue.inputnumber,
Checkbox: primevue.checkbox,
Dropdown: primevue.dropdown,
Textarea: primevue.textarea
Textarea: primevue.textarea,
VueDatePicker
},
props: {
projektarbeit: {
@@ -82,6 +83,17 @@ export const AbgabeStudentDetail = {
downloadAbgabe(termin) {
this.$fhcApi.factory.lehre.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid)
},
formatDate(dateParam) {
const date = new Date(dateParam)
// handle missing leading 0
const padZero = (num) => String(num).padStart(2, '0');
const month = padZero(date.getMonth() + 1); // Months are zero-based
const day = padZero(date.getDate());
const year = date.getFullYear();
return `${day}.${month}.${year}`;
},
upload(termin) {
if (!this.validate(termin))
@@ -147,7 +159,7 @@ export const AbgabeStudentDetail = {
color = 'green'
}
return 'font-color: ' + fontColor + '; background-color: ' + color
return `font-color: ${fontColor} ; background-color: ${color}; border-radius: 50%;`
},
openBeurteilungLink(link) {
window.open(link, '_blank')
@@ -193,9 +205,9 @@ export const AbgabeStudentDetail = {
<div id="uploadWrapper">
<div class="row" style="margin-bottom: 12px;">
<div style="width: 100px">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-4">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col">
{{$p.t('abgabetool/c4fileupload')}}
@@ -205,33 +217,42 @@ export const AbgabeStudentDetail = {
<div style="width: 100px" class="d-flex justify-content-center align-items-center">
<p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>
</div>
<div class="col-1 d-flex justify-content-center align-items-center">
<div class="col-2 d-flex justify-content-center align-items-center">
<div :style="getDateStyle(termin)">
{{ termin.datum?.split("-").reverse().join(".") }}
<VueDatePicker
style="width: 95%;"
v-model="termin.datum"
:clearable="false"
:disabled="true"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</div>
</div>
<div class="col-1 d-flex justify-content-center align-items-center">{{ termin.bezeichnung }}</div>
<div class="col-2 d-flex justify-content-center align-items-center">{{ termin.kurzbz }}</div>
<div class="col-2 d-flex justify-content-center align-items-center">{{ termin.bezeichnung }}</div>
<div class="col-4 d-flex justify-content-center align-items-center">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="3" cols="60" :disabled="true"></Textarea>
</div>
<div class="col-1 d-flex justify-content-center align-items-center">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
<i class="fa-solid fa-file-pdf"></i>
</a>
</div>
<div class="col-6">
<div class="col-2" v-if="!viewMode">
<div class="row">
<div class="col-6">
<Upload v-if="termin && termin.allowedToUpload" :disabled="viewMode" accept=".pdf" v-model="termin.file"></Upload>
<Upload v-if="termin && termin.allowedToUpload" accept=".pdf" v-model="termin.file"></Upload>
</div>
<div class="col-6">
<button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload || viewMode">
<button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload">
Upload
<i style="margin-left: 8px" class="fa-solid fa-upload"></i>
</button>
</div>
</div>
</div>
</div>
</div>
@@ -72,7 +72,6 @@ export const AbgabetoolMitarbeiter = {
{
event: "cellClick",
handler: async (e, cell) => {
debugger
if(cell.getColumn().getField() === "details") {
this.setDetailComponent(cell.getValue())
}
@@ -84,7 +83,6 @@ export const AbgabetoolMitarbeiter = {
event: "rowClick",
handler: async (e, row) => {
debugger
e.stopPropagation()
}
@@ -93,7 +91,6 @@ export const AbgabetoolMitarbeiter = {
event: "rowSelected",
handler: async (row) => {
debugger
}
},
@@ -123,7 +120,8 @@ export const AbgabetoolMitarbeiter = {
setDetailComponent(details){
this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data.retval
pa.abgabetermine = res.data[0].retval
pa.isCurrent = res.data[1]
pa.abgabetermine.push({ // new abgatermin row
'paabgabe_id': -1,
@@ -153,9 +151,9 @@ export const AbgabetoolMitarbeiter = {
this.selectedProjektarbeit = pa
Vue.nextTick(()=>{
this.$refs.verticalsplit.showBoth()
})
this.$refs.verticalsplit.showBoth()
})
},
@@ -1,11 +1,13 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import AbgabeDetail from "./AbgabeStudentDetail.js";
import VerticalSplit from "../../verticalsplit/verticalsplit.js";
export const AbgabetoolStudent = {
name: "AbgabetoolStudent",
components: {
CoreFilterCmpt,
AbgabeDetail
AbgabeDetail,
VerticalSplit
},
props: {
student_uid_prop: {
@@ -22,6 +24,7 @@ export const AbgabetoolStudent = {
},
data() {
return {
tabulatorUuid: Vue.ref(0),
domain: '',
student_uid: null,
detail: null,
@@ -30,7 +33,7 @@ export const AbgabetoolStudent = {
tableBuiltResolve: null,
tableBuiltPromise: null,
abgabeTableOptions: {
height: 300,
height: 700,
index: 'projektarbeit_id',
layout: 'fitColumns',
placeholder: this.$p.t('global/noDataAvailable'),
@@ -85,7 +88,7 @@ export const AbgabetoolStudent = {
setDetailComponent(details){
this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data.retval
pa.abgabetermine = res.data[0].retval
pa.abgabetermine.forEach(termin => {
termin.file = []
termin.allowedToUpload = true
@@ -103,6 +106,10 @@ export const AbgabetoolStudent = {
pa.student_uid = this.student_uid
this.selectedProjektarbeit = pa
this.$refs.verticalsplit.showBoth()
})
},
@@ -195,12 +202,23 @@ export const AbgabetoolStudent = {
handleUuidDefined(uuid) {
this.tabulatorUuid = uuid
},
calcMaxTableHeight() {
const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
const tableDataSet = document.getElementById('filterTableDataset' + tableID);
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.abgabeTableOptions.height = window.visualViewport.height - rect.top
this.$refs.abgabeTable.tabulator.setHeight(this.abgabeTableOptions.height)
},
async setupMounted() {
this.tableBuiltPromise = new Promise(this.tableResolve)
await this.tableBuiltPromise
this.loadProjektarbeiten()
this.$refs.verticalsplit.collapseBottom()
this.calcMaxTableHeight()
}
},
watch: {
@@ -218,22 +236,28 @@ export const AbgabetoolStudent = {
this.setupMounted()
},
template: `
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
:title="''"
ref="abgabeTable"
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
/>
<hr>
<div v-show="selectedProjektarbeit">
<AbgabeDetail :viewMode="isViewMode" :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</div>
<vertical-split ref="verticalsplit">
<template #top>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
@uuidDefined="handleUuidDefined"
:title="''"
ref="abgabeTable"
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
/>
</template>
<template #bottom>
<div v-show="selectedProjektarbeit">
<AbgabeDetail :viewMode="isViewMode" :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</div>
</template>
</vertical-split>
`,
};
@@ -1,4 +1,3 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
export const Raumsuche = {
+5 -1
View File
@@ -20,6 +20,10 @@ export default {
accept: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
}
},
methods: {
@@ -88,7 +92,7 @@ export default {
},
template: `
<div class="form-upload-dms">
<input ref="upload" class="form-control" :accept="accept" :class="inputClass" :id="id" :name="name" :multiple="multiple" type="file" @change="addFiles">
<input ref="upload" class="form-control" :disabled="disabled" :accept="accept" :class="inputClass" :id="id" :name="name" :multiple="multiple" type="file" @change="addFiles">
<ul v-if="modelValue.length && multiple && !noList" :accept="accept" class="list-unstyled m-0">
<dms-item
v-for="(file, index) in modelValue"
+60
View File
@@ -37879,6 +37879,66 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4abgabeMitarbeiterbereich',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Dokumentabgabe - Mitarbeiterbereich",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "File submission - Employee area",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'abgabe',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Abgabe",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Submission',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'genericDeleted',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => '{0} gelöscht',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => '{0} deleted',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'lehre',