mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-01 20:29:29 +00:00
add semester filter for tags
- in detailheader: use currentSem for manual triggering and refactor formatter - in list: preload list of ids with start and end for tabulator formatter to enable filtering
This commit is contained in:
@@ -15,11 +15,12 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
'getTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'getTags' => self::BERECHTIGUNG_KURZBZ,
|
||||
'addTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
|
||||
'updateTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'doneTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'deleteTag' => self::BERECHTIGUNG_KURZBZ,
|
||||
'getAllTags' => self::BERECHTIGUNG_KURZBZ,
|
||||
'getSemDates' => self::BERECHTIGUNG_KURZBZ,
|
||||
'getAllStartAndEndAutomatedTags' => self::BERECHTIGUNG_KURZBZ,
|
||||
'rebuildTagsForTypeId' => self::BERECHTIGUNG_KURZBZ,
|
||||
];
|
||||
|
||||
@@ -341,16 +342,46 @@ class Tag_Controller extends FHCAPI_Controller
|
||||
{
|
||||
$id = $this->input->post('id');
|
||||
$typeId = $this->input->post('typeId');
|
||||
$semester = $this->input->post('sem');
|
||||
|
||||
$result = $this->taglib->rebuildTagsForTypeId($typeId, $id, $semester);
|
||||
|
||||
$result = $this->taglib->rebuildTagsForTypeId($typeId, $id);
|
||||
//TODO (refactor; um semester, studiengang_kz)
|
||||
//$result = $this->taglib->rebuildTagsForTypeId($params);
|
||||
if (isError($result))
|
||||
return error ('Error occurred during updateAutomatedTags');
|
||||
|
||||
$this->terminateWithSuccess($result);
|
||||
}
|
||||
|
||||
public function getSemDates()
|
||||
{
|
||||
$studiensemester_kurzbz = $this->input->get('semester');
|
||||
$this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
|
||||
$result = $this->StudiensemesterModel->loadWhere(array('studiensemester_kurzbz' => $studiensemester_kurzbz));
|
||||
if (isError($result))
|
||||
return error('Error occurred during retrieving studiensemester');
|
||||
$data = getData($result);
|
||||
$this->terminateWithSuccess(current($data));
|
||||
|
||||
}
|
||||
|
||||
public function getAllStartAndEndAutomatedTags()
|
||||
{
|
||||
$this->NotizModel->addSelect('notiz_id as id');
|
||||
$this->NotizModel->addSelect('start');
|
||||
$this->NotizModel->addSelect('ende');
|
||||
$this->NotizModel->addSelect('typ');
|
||||
|
||||
$result = $this->NotizModel->loadWhere(array(
|
||||
'titel' => 'TAG',
|
||||
'verfasser_uid' => 'sftest'
|
||||
));
|
||||
|
||||
if (isError($result))
|
||||
return error('Error occurred during retrieving intervalls automated tags');
|
||||
$data = getData($result);
|
||||
$this->terminateWithSuccess($data);
|
||||
}
|
||||
|
||||
private function _setAuthUID()
|
||||
{
|
||||
$this->_uid = getAuthUID();
|
||||
|
||||
@@ -241,21 +241,18 @@ class TagLib
|
||||
/*
|
||||
* main function for rebuild Tags for typeId
|
||||
* */
|
||||
public function rebuildTagsForTypeId($typeId, $id) //TODO aktSem of frontend
|
||||
public function rebuildTagsForTypeId($typeId, $id, $studiensemester_kurzbz)
|
||||
{
|
||||
$automatedTagsRes = $this->_ci->NotiztypModel->loadWhere(array('automatisiert' => true, 'taglib IS NOT NULL' => null));
|
||||
$automatedTags = hasData($automatedTagsRes) ? getData($automatedTagsRes) : [];
|
||||
|
||||
//TODO change to chosen Studiensemester in frontend
|
||||
$result = $this->_ci->StudiensemesterModel->getAktOrNextSemester();
|
||||
$result = $this->_ci->StudiensemesterModel->load($studiensemester_kurzbz);
|
||||
if (isError($result))
|
||||
return error('Error occurred during retrieving studiensemester');
|
||||
if (empty($result->retval) || !isset($result->retval[0])) {
|
||||
return error('No studiensemester found');
|
||||
}
|
||||
$studiensemester_kurzbz = $result->retval[0]->studiensemester_kurzbz ?? null;
|
||||
|
||||
//for checkDelete
|
||||
$startSem = $result->retval[0]->start ?? null;
|
||||
$endeSem = $result->retval[0]->ende ?? null;
|
||||
$return = [];
|
||||
|
||||
@@ -61,6 +61,21 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
getSemDates(studiensemester_kurzbz){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/Tags/getSemDates',
|
||||
params: studiensemester_kurzbz
|
||||
};
|
||||
},
|
||||
|
||||
getAllStartAndEndAutomatedTags(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/Tags/getAllStartAndEndAutomatedTags',
|
||||
};
|
||||
},
|
||||
|
||||
rebuildTagsforTypeId(data){
|
||||
return {
|
||||
method: 'post',
|
||||
|
||||
@@ -49,6 +49,10 @@ export default {
|
||||
from: 'configStvTagsEnabled',
|
||||
default: false
|
||||
},
|
||||
currentSemester: {
|
||||
from: 'currentSemester',
|
||||
required: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
appRoot() {
|
||||
@@ -85,6 +89,7 @@ export default {
|
||||
}
|
||||
|
||||
if(this.tagsEnabled) {
|
||||
this.getSemesterDates(this.currentSemester);
|
||||
this.loadTagsAndRender(this.headerData[0].prestudent_id);
|
||||
}
|
||||
|
||||
@@ -113,6 +118,14 @@ export default {
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
currentSemester: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.getSemesterDates(newVal);
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
}
|
||||
},
|
||||
data(){
|
||||
@@ -123,7 +136,8 @@ export default {
|
||||
isFetchingIssues: false,
|
||||
tagEndpoint: ApiTag,
|
||||
tagData: null,
|
||||
rebuildData: null
|
||||
rebuildData: null,
|
||||
semDates: {}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -219,7 +233,9 @@ export default {
|
||||
prestudent_id,
|
||||
this.tagData,
|
||||
this.$refs.tagComponent,
|
||||
'prestudent_id'
|
||||
'prestudent_id',
|
||||
this.semDates.start,
|
||||
this.semDates.ende
|
||||
);
|
||||
|
||||
this.$refs.tagWrapper.innerHTML = '';
|
||||
@@ -248,7 +264,8 @@ export default {
|
||||
rebuildPrestudentTags(){
|
||||
const params = {
|
||||
id : this.headerData[0].prestudent_id,
|
||||
typeId: 'prestudent_id'
|
||||
typeId: 'prestudent_id',
|
||||
sem: this.currentSemester
|
||||
};
|
||||
|
||||
return this.$api
|
||||
@@ -259,6 +276,17 @@ export default {
|
||||
this.reload();
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
getSemesterDates(semester){
|
||||
const params = {
|
||||
studiensemester_kurzbz: semester
|
||||
};
|
||||
return this.$api
|
||||
.call(ApiTag.getSemDates({semester}))
|
||||
.then(result => {
|
||||
this.semDates = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
@@ -339,8 +367,10 @@ export default {
|
||||
v-if="tagsEnabled"
|
||||
@click="rebuildPrestudentTags"
|
||||
class="btn btn-outline btn-light mb-1"
|
||||
title="Automatische Tags neu laden">
|
||||
:title="'Automatische Tags fuer ' + currentSemester + ' neu laden'"
|
||||
>
|
||||
<i class="fa-solid fa-refresh"></i></button>
|
||||
<span>{{currentSemester}}</span>
|
||||
</div>
|
||||
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
|
||||
</div>
|
||||
|
||||
@@ -237,7 +237,9 @@ export default {
|
||||
dragSource: [],
|
||||
oldScrollUrl: '',
|
||||
oldScrollLeft: 0,
|
||||
oldScrollTop: 0
|
||||
oldScrollTop: 0,
|
||||
semDates: {}, //TODO(Manu) check injections
|
||||
intervalMap: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -293,7 +295,12 @@ export default {
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
|
||||
if(this.tagsEnabled) {
|
||||
this.getSemesterDates(this.currentSemester);
|
||||
|
||||
this.loadIntervals(); //preload
|
||||
|
||||
const coltags = {
|
||||
title: 'Tags',
|
||||
field: 'tags',
|
||||
@@ -301,7 +308,36 @@ export default {
|
||||
headerFilter: "input",
|
||||
headerFilterFunc: tagHeaderFilter,
|
||||
headerFilterFuncParams: {field: 'tags'},
|
||||
formatter: (cell) => tagFormatter(cell, this.$refs.tagComponent),
|
||||
//formatter: (cell) => tagFormatter(cell, this.$refs.tagComponent), //prev. Version without filter
|
||||
formatter: (cell) => {
|
||||
const raw = cell.getValue();
|
||||
|
||||
const tags =
|
||||
Array.isArray(raw)
|
||||
? raw
|
||||
: (typeof raw === 'string'
|
||||
? JSON.parse(raw)
|
||||
: []);
|
||||
|
||||
const id = tags?.[0]?.id;
|
||||
|
||||
const interval = id
|
||||
? this.intervalMap[id]
|
||||
: null;
|
||||
|
||||
const enrichedTags = {
|
||||
tags: tags,
|
||||
start: interval?.start || null,
|
||||
ende: interval?.end || null,
|
||||
};
|
||||
|
||||
return tagFormatter(
|
||||
enrichedTags,
|
||||
this.$refs.tagComponent,
|
||||
this.semDates.start,
|
||||
this.semDates.ende
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
};
|
||||
this.tabulatorOptions.columns.splice(2, 0, coltags);
|
||||
@@ -312,9 +348,40 @@ export default {
|
||||
if (n !== o && o !== undefined && this.$refs.table.tableBuilt) {
|
||||
this.translateTabulator();
|
||||
}
|
||||
},
|
||||
currentSemester: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.getSemesterDates(newVal);
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadIntervals() {
|
||||
return this.$api
|
||||
.call(ApiTag.getAllStartAndEndAutomatedTags())
|
||||
.then(result => {
|
||||
const data = result.data || [];
|
||||
this.intervalMap = {};
|
||||
data.forEach(item => {
|
||||
this.intervalMap[item.id] = item;
|
||||
});
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
getSemesterDates(semester){ //TODO(check for injections)
|
||||
const params = {
|
||||
studiensemester_kurzbz: semester
|
||||
};
|
||||
return this.$api
|
||||
.call(ApiTag.getSemDates({semester}))
|
||||
.then(result => {
|
||||
this.semDates = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
translateTabulator() {
|
||||
this.$p
|
||||
.loadCategory(['global', 'person', 'lehre', 'ui', 'profilUpdate', 'admission', 'stv'])
|
||||
@@ -398,7 +465,6 @@ export default {
|
||||
//for tags
|
||||
this.selectedRows = this.$refs.table.tabulator.getSelectedRows();
|
||||
this.selectedColumnValues = this.selectedRows.filter(row => row.getData().prestudent_id !== undefined && row.getData().prestudent_id).map(row => row.getData().prestudent_id);
|
||||
|
||||
this.$emit('update:selected', data);
|
||||
},
|
||||
autoSelectRows(data) {
|
||||
@@ -601,6 +667,7 @@ export default {
|
||||
// TODO(chris): filter component column chooser has no accessibilty features
|
||||
template: `
|
||||
<div class="stv-list h-100 pt-3">
|
||||
test manu: currentSEM: {{semDates.start}} - {{semDates.ende}}
|
||||
<div
|
||||
class="tabulator-container d-flex flex-column h-100"
|
||||
:class="{'has-filter': filter.length}"
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
|
||||
export function idTagFormatter (id, tagData, tagComponent, typeId)
|
||||
export function idTagFormatter (id, tagData, tagComponent, typeId, semesterStart=null, semesterEnd=null)
|
||||
{
|
||||
if (!id) return;
|
||||
|
||||
const hasSemesterFilter = !!(semesterStart && semesterEnd);
|
||||
|
||||
const semStart = hasSemesterFilter ? new Date(semesterStart) : null;
|
||||
const semEnd = hasSemesterFilter ? new Date(semesterEnd) : null;
|
||||
|
||||
const parsedTags = tagData.map(tag => ({
|
||||
id: tag.notiz_id,
|
||||
typ_kurzbz: tag.titel?.toLowerCase(),
|
||||
@@ -12,9 +15,33 @@ export function idTagFormatter (id, tagData, tagComponent, typeId)
|
||||
style: tag.style,
|
||||
done: tag.done,
|
||||
automatisiert: tag.automatisiert,
|
||||
typeId: id
|
||||
typeId: id,
|
||||
validFrom: tag.start ? new Date(tag.start) : null,
|
||||
validTo: tag.ende ? new Date(tag.ende) : null
|
||||
}));
|
||||
|
||||
const isInSemester = (tag) => {
|
||||
if (!hasSemesterFilter) return true;
|
||||
|
||||
if (!tag.validFrom && !tag.validTo) return true;
|
||||
|
||||
if (!tag.validFrom && !tag.validTo) return true;
|
||||
|
||||
if (tag.validFrom && tag.validTo) {
|
||||
return tag.validFrom <= semEnd && tag.validTo >= semStart;
|
||||
}
|
||||
|
||||
if (tag.validFrom && !tag.validTo) {
|
||||
return tag.validFrom <= semEnd;
|
||||
}
|
||||
|
||||
if (!tag.validFrom && tag.validTo) {
|
||||
return tag.validTo >= semStart;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
let container = document.createElement('div');
|
||||
container.className = "d-flex gap-1";
|
||||
|
||||
@@ -23,7 +50,9 @@ export function idTagFormatter (id, tagData, tagComponent, typeId)
|
||||
const renderTags = () => {
|
||||
container.innerHTML = '';
|
||||
|
||||
let filtered = parsedTags.filter(t => t != null);
|
||||
let filtered = parsedTags
|
||||
.filter(t => t != null)
|
||||
.filter(isInSemester);
|
||||
|
||||
filtered.sort((a, b) => {
|
||||
let adone = a.done ? 1 : 0;
|
||||
|
||||
@@ -1,28 +1,115 @@
|
||||
export function tagFormatter(cell, tagComponent)
|
||||
{
|
||||
export function tagFormatter(
|
||||
cell,
|
||||
tagComponent,
|
||||
semesterStart = null,
|
||||
semesterEnd = null
|
||||
) {
|
||||
|
||||
// support both call versions
|
||||
// 1. previous Tabulator cell: old version
|
||||
// 2. custom enriched object: with start and end for tags plus semesterDates
|
||||
// for check if valid tag
|
||||
|
||||
const isTabulatorCell =
|
||||
cell && typeof cell.getValue === 'function';
|
||||
|
||||
const normalized = isTabulatorCell
|
||||
? {
|
||||
tags: cell.getValue() || [],
|
||||
start: null,
|
||||
ende: null,
|
||||
rowData: cell.getRow().getData(),
|
||||
}
|
||||
: {
|
||||
tags: cell.tags || [],
|
||||
start: cell.start || null,
|
||||
ende: cell.ende || null,
|
||||
rowData: cell.rowData || {},
|
||||
};
|
||||
|
||||
const tags = normalized.tags || [];
|
||||
|
||||
if (!tags.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const mappedData = tagComponent.tags.map(tag => ({
|
||||
typ_kurzbz: tag.tag_typ_kurzbz,
|
||||
automatisiert: tag.automatisiert
|
||||
automatisiert: tag.automatisiert,
|
||||
validFrom: normalized.start
|
||||
? new Date(normalized.start)
|
||||
: null,
|
||||
validTo: normalized.ende
|
||||
? new Date(normalized.ende)
|
||||
: null
|
||||
}));
|
||||
|
||||
const hasSemesterFilter =
|
||||
!!(semesterStart && semesterEnd);
|
||||
|
||||
let tags = cell.getValue();
|
||||
if (!tags) return;
|
||||
const semStart = hasSemesterFilter ? new Date(semesterStart) : null;
|
||||
|
||||
const semEnd = hasSemesterFilter ? new Date(semesterEnd) : null;
|
||||
|
||||
const isInSemester = (tag) => {
|
||||
|
||||
if (!hasSemesterFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!tag.validFrom && !tag.validTo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tag.validFrom && tag.validTo) {
|
||||
return (
|
||||
tag.validFrom <= semEnd &&
|
||||
tag.validTo >= semStart
|
||||
);
|
||||
}
|
||||
|
||||
if (tag.validFrom && !tag.validTo) {
|
||||
return tag.validFrom <= semEnd;
|
||||
}
|
||||
|
||||
if (!tag.validFrom && tag.validTo) {
|
||||
return tag.validTo >= semStart;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// parse tags if needed
|
||||
let parsedTags =
|
||||
typeof tags === 'string'
|
||||
? JSON.parse(tags)
|
||||
: tags;
|
||||
|
||||
let container = document.createElement('div');
|
||||
container.className = "d-flex gap-1";
|
||||
|
||||
let parsedTags = JSON.parse(tags);
|
||||
let maxVisibleTags = 2;
|
||||
|
||||
const rowData = cell.getRow().getData();
|
||||
const rowData = normalized.rowData;
|
||||
|
||||
if (rowData._tagExpanded === undefined) {
|
||||
rowData._tagExpanded = false;
|
||||
}
|
||||
|
||||
const renderTags = () => {
|
||||
container.innerHTML = '';
|
||||
parsedTags = parsedTags.filter(item => item !== null);
|
||||
|
||||
parsedTags = parsedTags.filter(tag => {
|
||||
const mapped = mappedData.find(
|
||||
m => m.typ_kurzbz === tag.typ_kurzbz
|
||||
);
|
||||
|
||||
if (!mapped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isInSemester(mapped);
|
||||
});
|
||||
|
||||
parsedTags.sort((a, b) => {
|
||||
let adone = a.done ? 1 : 0;
|
||||
@@ -34,10 +121,14 @@ export function tagFormatter(cell, tagComponent)
|
||||
}
|
||||
return b.id - a.id;
|
||||
});
|
||||
const tagsToShow = rowData._tagExpanded ? parsedTags : parsedTags.slice(0, maxVisibleTags);
|
||||
|
||||
const tagsToShow = rowData._tagExpanded
|
||||
? parsedTags
|
||||
: parsedTags.slice(0, maxVisibleTags);
|
||||
|
||||
tagsToShow.forEach(tag => {
|
||||
if (!tag) return;
|
||||
|
||||
let tagElement = document.createElement('span');
|
||||
tagElement.innerText = tag.beschreibung;
|
||||
tagElement.title = tag.notiz;
|
||||
@@ -60,8 +151,10 @@ export function tagFormatter(cell, tagComponent)
|
||||
container.appendChild(tagElement);
|
||||
});
|
||||
|
||||
if (parsedTags.length > maxVisibleTags) {
|
||||
// show expand button
|
||||
if ( parsedTags.length > maxVisibleTags) {
|
||||
let toggle = document.createElement('button');
|
||||
|
||||
toggle.innerText = (rowData._tagExpanded ? '- ' : '+ ') + (parsedTags.length - maxVisibleTags);
|
||||
toggle.className = "display_all";
|
||||
toggle.title = rowData._tagExpanded ? "Tags ausblenden" : "Tags einblenden";
|
||||
|
||||
Reference in New Issue
Block a user