show Tags in CoreHeader

This commit is contained in:
ma0068
2026-03-31 12:48:27 +02:00
parent 5dbddb4beb
commit 50af6694d0
8 changed files with 257 additions and 14 deletions
+1 -1
View File
@@ -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],
+5 -5
View File
@@ -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');
+60 -1
View File
@@ -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();
+1 -1
View File
@@ -76,7 +76,7 @@
}
.tag_auto {
border: 2px solid #8BCF8A;
border: 1px dashed white;
}
.display_all {
+9
View File
@@ -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
};
}
};
@@ -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: `
<div class="core-header d-flex justify-content-start align-items-center w-100 overflow-auto pb-2 gap-3" style="max-height:9rem; min-width: 37.5rem;">
@@ -245,6 +309,15 @@ export default {
<span v-if="headerData[0].titelpost">, </span>
{{headerData[0].titelpost}}
</h2>
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="prestudentIds"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
></core-tag>
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
</div>
@@ -267,6 +340,7 @@ export default {
<strong v-if="headerData[0].statusofsemester" class="text-muted"> | Status </strong>
{{headerData[0].statusofsemester}}
</h5>
<div ref="tagWrapper"></div>
</div>
<div v-if="headerData.length == 1" class="col-md-1 d-flex flex-column align-items-end justify-content-start ms-auto">
+78
View File
@@ -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;
}
+25 -2
View File
@@ -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();