diff --git a/application/core/Tag_Controller.php b/application/core/Tag_Controller.php index 082cb9ac4..b321bac92 100644 --- a/application/core/Tag_Controller.php +++ b/application/core/Tag_Controller.php @@ -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(); diff --git a/application/libraries/TagLib.php b/application/libraries/TagLib.php index 2442b2fab..4eb8dedef 100644 --- a/application/libraries/TagLib.php +++ b/application/libraries/TagLib.php @@ -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 = []; diff --git a/public/js/api/factory/stv/tag.js b/public/js/api/factory/stv/tag.js index 26aacca69..3a3f78167 100644 --- a/public/js/api/factory/stv/tag.js +++ b/public/js/api/factory/stv/tag.js @@ -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', diff --git a/public/js/components/DetailHeader/DetailHeader.js b/public/js/components/DetailHeader/DetailHeader.js index 516ecdd87..076b7db56 100644 --- a/public/js/components/DetailHeader/DetailHeader.js +++ b/public/js/components/DetailHeader/DetailHeader.js @@ -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'" + > + {{currentSemester}}
unruly
diff --git a/public/js/components/Stv/Studentenverwaltung/List.js b/public/js/components/Stv/Studentenverwaltung/List.js index 4fe8b5eb6..d98883165 100644 --- a/public/js/components/Stv/Studentenverwaltung/List.js +++ b/public/js/components/Stv/Studentenverwaltung/List.js @@ -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: `
+ test manu: currentSEM: {{semDates.start}} - {{semDates.ende}}
({ 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; diff --git a/public/js/tabulator/formatter/tags.js b/public/js/tabulator/formatter/tags.js index b9578dbd4..d9a5244fd 100644 --- a/public/js/tabulator/formatter/tags.js +++ b/public/js/tabulator/formatter/tags.js @@ -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";