diff --git a/application/config/stv.php b/application/config/stv.php index 320121ba7..be49fed97 100644 --- a/application/config/stv.php +++ b/application/config/stv.php @@ -144,7 +144,7 @@ $config['stv_prestudent_tags'] = [ 'inwork_kf' => ['readonly' => false], 'dd_auto' => ['readonly' => false], 'wh_auto' => ['readonly' => false], - 'prewh_auto' => ['readonly' => false], + 'prewh_auto' => ['readonly' => true], //when readonly kein rendering für automatisierte tags 'out_auto' => ['readonly' => false], 'zgv_auto' => ['readonly' => false], 'wiedereinstieg_auto' => ['readonly' => false], diff --git a/application/controllers/jobs/TagJob.php b/application/controllers/jobs/TagJob.php index bad45dd04..93bcee521 100644 --- a/application/controllers/jobs/TagJob.php +++ b/application/controllers/jobs/TagJob.php @@ -8,6 +8,8 @@ class TagJob extends JOB_Controller { const BATCHUSER = 'sftest'; + const SEMESTER = 'SS2024'; //docker + //TODO semester /** * API constructor @@ -22,8 +24,6 @@ class TagJob extends JOB_Controller // Library $this->load->library('TagLib'); - // Tag-Helper - // Load Models $this->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); @@ -103,7 +103,7 @@ class TagJob extends JOB_Controller print_r(PHP_EOL . "create Tags wiederholer " . PHP_EOL); $result = $this->PrestudentstatusModel-> loadWhere(array( 'statusgrund_id' => 16, - 'studiensemester_kurzbz' => 'SS2026' + 'studiensemester_kurzbz' => self::SEMESTER )); $data = $result->retval; $ids = array_map(function($item) { @@ -126,7 +126,7 @@ class TagJob extends JOB_Controller $result = $this->PrestudentstatusModel-> loadWhere(array( 'statusgrund_id' => 15, - 'studiensemester_kurzbz' => 'SS2026' + 'studiensemester_kurzbz' => self::SEMESTER )); $data = $result->retval; $ids = array_map(function($item) { @@ -204,7 +204,7 @@ class TagJob extends JOB_Controller /** * delete All Automatic Tags */ - public function deleteAllAutomatedTags() + public function deleteAutomatedTags() { print_r( PHP_EOL . "Start Job delete ALL Automated Tags" . PHP_EOL); // $this->NotizModel->select('notiz_id'); diff --git a/application/core/Tag_Controller.php b/application/core/Tag_Controller.php index 5b9bac6c5..d3b071f96 100644 --- a/application/core/Tag_Controller.php +++ b/application/core/Tag_Controller.php @@ -19,6 +19,7 @@ class Tag_Controller extends FHCAPI_Controller 'updateTag' => self::BERECHTIGUNG_KURZBZ, 'doneTag' => self::BERECHTIGUNG_KURZBZ, 'deleteTag' => self::BERECHTIGUNG_KURZBZ, + 'getAllTags' => self::BERECHTIGUNG_KURZBZ, ]; $merged_permissions = array_merge($default_permissions, $permissions); @@ -65,6 +66,7 @@ class Tag_Controller extends FHCAPI_Controller array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $language. " as bezeichnung, tbl_notiz.notiz_id, tbl_notiz_typ.style, + tbl_notiz_typ.automatisiert, tbl_notiz.erledigt as done, tbl_notiz.insertamum, tbl_notiz.updateamum, @@ -82,6 +84,7 @@ class Tag_Controller extends FHCAPI_Controller $notiz = $this->NotizModel->loadWhere(array('notiz_id' => $id)); + $this->terminateWithSuccess(hasData($notiz) ? getData($notiz)[0] : array()); } @@ -92,7 +95,8 @@ class Tag_Controller extends FHCAPI_Controller array_to_json(bezeichnung_mehrsprachig::varchar[])->>0 as bezeichnung, style, beschreibung, - tag + tag, + automatisiert ' ); $this->NotiztypModel->addOrder('prioritaet'); @@ -271,6 +275,61 @@ class Tag_Controller extends FHCAPI_Controller $this->terminateWithSuccess($deleteNotiz); } + public function getAllTags($readonly_tags = false){ + $language = $this->_getLanguageIndex(); + $prestudent_id = $this->input->get('prestudent_id'); + // $this->terminateWithError("id: " . $prestudent_id, self::ERROR_TYPE_GENERAL); + + //TODO check for readonly: necessary? + if (is_array($readonly_tags) && !isEmptyArray($readonly_tags)) + { + $readonly_tags = $this->_filterTag($readonly_tags, true); + + foreach ($readonly_tags as $key => $tag) + { + $readonly_tags[$key] = $this->NotizModel->db->escape($tag); + } + $tags = '(' . implode(',', $readonly_tags) . ')'; + + $this->NotizModel->addSelect(" + CASE + WHEN tbl_notiz_typ.typ_kurzbz IN $tags + THEN TRUE + ELSE FALSE + END as readonly + "); + } + + $this->NotizModel->addSelect( + "tbl_notiz.titel, + tbl_notiz.text, + array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $language. " as bezeichnung, + tbl_notiz.notiz_id, + tbl_notiz_typ.style, + tbl_notiz_typ.automatisiert, + tbl_notiz.erledigt as done, + tbl_notiz.insertamum, + tbl_notiz.updateamum, + (verfasserperson.vorname || ' ' || verfasserperson.nachname || ' ' || '(' || verfasserbenutzer.uid || ')') as verfasser, + (bearbeiterperson.vorname || ' ' || bearbeiterperson.nachname || ' ' || '(' || bearbeiterbenutzer.uid || ')') as bearbeiter + " + ); + $this->NotizModel->addJoin('public.tbl_notiz_typ', 'public.tbl_notiz.typ = public.tbl_notiz_typ.typ_kurzbz'); + + $this->NotizModel->addJoin('public.tbl_benutzer verfasserbenutzer', 'tbl_notiz.verfasser_uid = verfasserbenutzer.uid', 'LEFT'); + $this->NotizModel->addJoin('public.tbl_person verfasserperson', 'verfasserbenutzer.person_id = verfasserperson.person_id', 'LEFT'); + + $this->NotizModel->addJoin('public.tbl_benutzer bearbeiterbenutzer', 'tbl_notiz.bearbeiter_uid = bearbeiterbenutzer.uid', 'LEFT'); + $this->NotizModel->addJoin('public.tbl_person bearbeiterperson', 'bearbeiterbenutzer.person_id = bearbeiterperson.person_id', 'LEFT'); + + $this->NotizModel->addJoin('public.tbl_notizzuordnung notizzuordnung', 'tbl_notiz.notiz_id = notizzuordnung.notiz_id'); + + $notiz = $this->NotizModel->loadWhere(array('prestudent_id' => $prestudent_id)); + + + $this->terminateWithSuccess(hasData($notiz) ? getData($notiz) : array()); + } + private function _setAuthUID() { $this->_uid = getAuthUID(); diff --git a/public/css/tags.css b/public/css/tags.css index bb122d6ed..995a7d43a 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -76,7 +76,7 @@ } .tag_auto { - border: 2px solid #8BCF8A; + border: 1px dashed white; } .display_all { diff --git a/public/js/api/factory/stv/tag.js b/public/js/api/factory/stv/tag.js index 29675f6d4..a5db6cb73 100644 --- a/public/js/api/factory/stv/tag.js +++ b/public/js/api/factory/stv/tag.js @@ -51,4 +51,13 @@ export default { params: data }; }, + + //TODO expand to other types + getAllTagsPrestudent(prestudent_id){ + return { + method: 'get', + url: 'api/frontend/v1/stv/Tags/getAllTags', + params: prestudent_id + }; + } }; \ No newline at end of file diff --git a/public/js/components/DetailHeader/DetailHeader.js b/public/js/components/DetailHeader/DetailHeader.js index 0b846df9c..3207cddaa 100644 --- a/public/js/components/DetailHeader/DetailHeader.js +++ b/public/js/components/DetailHeader/DetailHeader.js @@ -1,11 +1,15 @@ import ApiDetailHeader from "../../api/factory/detailHeader.js"; import ApiHandleFoto from "../../api/factory/fotoHandling.js"; import ModalUploadFoto from "./Modal/UploadFoto.js"; +import CoreTag from "../Tag/Tag.js"; +import ApiTag from "../../api/factory/stv/tag.js"; +import { idTagFormatter } from "../Tag/tagFormatter.js"; export default { name: 'DetailHeader', components: { - ModalUploadFoto + ModalUploadFoto, + CoreTag }, props: { headerData: { @@ -40,6 +44,12 @@ export default { } } }, + inject: { + tagsEnabled: { + from: 'configStvTagsEnabled', + default: false + }, + }, computed: { appRoot() { return FHC_JS_DATA_STORAGE_OBJECT.app_root; @@ -61,13 +71,23 @@ export default { hasTileUIDSlot() { return !!this.$slots.uid }, - + prestudentIds() { + if (this.headerData[0].prestudent_id) + { + return [this.headerData[0].prestudent_id]; + } + }, }, created(){ if (this.typeHeader === 'student') { if (!this.headerData) { throw new Error('[DetailHeader] "headerData" is required.') } + + if(this.tagsEnabled) { + this.loadTagsAndRender(this.headerData[0].prestudent_id); + } + } else if (this.typeHeader === 'mitarbeiter') { if (!this.person_id || !this.mitarbeiter_uid || !this.domain) { throw new Error( @@ -86,13 +106,23 @@ export default { }, deep: true, }, + headerData: { + handler(newVal) { + if(this.tagsEnabled) { + this.loadTagsAndRender(this.headerData[0].prestudent_id); + } + }, + deep: true, + } }, data(){ return{ headerDataMa: {}, departmentData: {}, leitungData: {}, - isFetchingIssues: false + isFetchingIssues: false, + tagEndpoint: ApiTag, + tagData: null, }; }, methods: { @@ -179,7 +209,41 @@ export default { } else { return 'data:image/jpeg;base64,' + foto; } - } + }, + //methods tags + async loadTagsAndRender(prestudent_id) { + await this.getAllTags(prestudent_id); + + const container = idTagFormatter( + prestudent_id, + this.tagData, + this.$refs.tagComponent, + 'prestudent_id' + ); + + this.$refs.tagWrapper.innerHTML = ''; + this.$refs.tagWrapper.appendChild(container); + }, + getAllTags(prestudent_id){ + return this.$api + .call(ApiTag.getAllTagsPrestudent({prestudent_id})) + .then(result => { + this.tagData = result.data; + }) + .catch(this.$fhcAlert.handleSystemError); + }, + addedTag(addedTag) + { + this.reload(); + }, + deletedTag(id) + { + this.reload(); + }, + updatedTag(updatedTag) + { + this.reload(); + }, }, template: `
@@ -245,6 +309,15 @@ export default { , {{headerData[0].titelpost}} +
unruly
@@ -267,6 +340,7 @@ export default { | Status {{headerData[0].statusofsemester}} +
diff --git a/public/js/components/Tag/tagFormatter.js b/public/js/components/Tag/tagFormatter.js new file mode 100644 index 000000000..03f067596 --- /dev/null +++ b/public/js/components/Tag/tagFormatter.js @@ -0,0 +1,78 @@ + + +export function idTagFormatter (id, tagData, tagComponent, typeId) +{ + if (!id) return; + + const parsedTags = tagData.map(tag => ({ + id: tag.notiz_id, + typ_kurzbz: tag.titel?.toLowerCase(), + beschreibung: tag.bezeichnung, + notiz: tag.text || "", + style: tag.style, + done: tag.done, + automatisiert: tag.automatisiert, + typeId: id + })); + + let container = document.createElement('div'); + container.className = "d-flex gap-1"; + + let maxVisibleTags = 5; + let expanded = false; + const renderTags = () => { + container.innerHTML = ''; + + let filtered = parsedTags.filter(t => t != null); + + filtered.sort((a, b) => { + let adone = a.done ? 1 : 0; + let bdone = b.done ? 1 : 0; + + if (adone !== bdone) return adone - bdone; + return b.id - a.id; + }); + + const tagsToShow = expanded + ? filtered + : filtered.slice(0, maxVisibleTags); + + tagsToShow.forEach(tag => { + let tagElement = document.createElement('span'); + tagElement.innerText = tag.beschreibung; + tagElement.title = tag.notiz; + tagElement.className = "tag " + tag.style; + + if (tag.done) { + tagElement.className += " tag_done"; + } + if (tag.automatisiert) + tagElement.className += " tag_auto"; + + tagElement.addEventListener('click', (event) => { + event.stopPropagation(); + event.preventDefault(); + tagComponent.editTag(tag.id); + }); + + container.appendChild(tagElement); + }); + + if (filtered.length > maxVisibleTags) { + let toggle = document.createElement('button'); + toggle.innerText = (expanded ? '- ' : '+ ') + (filtered.length - maxVisibleTags); + toggle.className = "display_all"; + + toggle.addEventListener('click', () => { + expanded = !expanded; + renderTags(); + }); + + container.appendChild(toggle); + } + }; + + renderTags(); + + return container; +} \ No newline at end of file diff --git a/public/js/tabulator/formatter/tags.js b/public/js/tabulator/formatter/tags.js index f68fafaf8..855bdba82 100644 --- a/public/js/tabulator/formatter/tags.js +++ b/public/js/tabulator/formatter/tags.js @@ -1,5 +1,13 @@ export function tagFormatter(cell, tagComponent) { + //TODO readOnlyComponents nicht in der TagComponent enthalten + //console.log(JSON.stringify(tagComponent)); + const mappedData = tagComponent.tags.map(tag => ({ + typ_kurzbz: tag.tag_typ_kurzbz, + automatisiert: tag.automatisiert + })); + + let tags = cell.getValue(); if (!tags) return; @@ -32,14 +40,29 @@ export function tagFormatter(cell, tagComponent) tagsToShow.forEach(tag => { if (!tag) return; + let tagElement = document.createElement('span'); tagElement.innerText = tag.beschreibung; tagElement.title = tag.notiz; tagElement.className = "tag " + tag.style; if (tag.done) tagElement.className += " tag_done"; - //TODO automated styling - if (tag.automatisiert) tagElement.className += " tag_done"; + const tagDef = mappedData.find(t => t.typ_kurzbz === tag.typ_kurzbz); + + if (tagDef?.automatisiert) + tagElement.className += " tag_auto"; + +/* TODO delete Testoutputs + if (!tagDef) { + console.log(tag.typ_kurzbz + " nicht in TagComponent enthalten"); + tagElement.className += " tag_auto"; + } + else if (tagDef?.automatisiert) { + console.log(tag.typ_kurzbz + " ist automatisiert"); + tagElement.className += " tag_auto"; + } else + console.log (tag.typ_kurzbz + " nicht automatisiert"); +*/ tagElement.addEventListener('click', (event) => { event.stopPropagation();