diff --git a/application/controllers/api/frontend/v1/stv/Vertrag.php b/application/controllers/api/frontend/v1/stv/Vertrag.php index f94fe795e..c2b0f713c 100644 --- a/application/controllers/api/frontend/v1/stv/Vertrag.php +++ b/application/controllers/api/frontend/v1/stv/Vertrag.php @@ -76,9 +76,7 @@ class Vertrag extends FHCAPI_Controller if (isError($allOe)) $this->terminateWithError(getError($allOe), self::ERROR_TYPE_GENERAL); - $allOe = hasData($allOe) ? getData($allOe) : []; - - $this->addMeta('oe', $allOe); + $allOe = hasData($allOe) ? array_column(getData($allOe), 'oe_kurzbz') : []; // * then check if the user has permissions to cancel the corresponding lv-organisational units if (!$this->permissionlib->isBerechtigtMultipleOe('admin', $allOe, 'suid') && diff --git a/application/models/ressource/Stundenplan_model.php b/application/models/ressource/Stundenplan_model.php index 067e2b790..d0a97ed9d 100644 --- a/application/models/ressource/Stundenplan_model.php +++ b/application/models/ressource/Stundenplan_model.php @@ -470,12 +470,12 @@ class Stundenplan_model extends DB_Model } foreach($studentlehrverbaende[$sem_date] as $key=>$lehrverband) { - $query .= "((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")"; + $query .= "(((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")"; // Eintraege fuer den ganzen Verband $query .= "OR (sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND (sp.gruppe is null OR sp.gruppe='') AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")"; // Eintraege fuer das ganze Semester $query .= "OR (sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND (sp.verband is null OR sp.verband='') AND sp.datum BETWEEN ".$this->escape($sem_date_range->start) - ." AND ".$this->escape($sem_date_range->ende).")". $stringGroupLv. ")"; + ." AND ".$this->escape($sem_date_range->ende).")) AND gruppe_kurzbz is null)"; $query .="OR"; } diff --git a/application/models/system/Message_model.php b/application/models/system/Message_model.php index e0a185f9b..19129b606 100644 --- a/application/models/system/Message_model.php +++ b/application/models/system/Message_model.php @@ -242,74 +242,89 @@ class Message_model extends DB_Model */ public function getMessagesForTable($person_id, $offset, $limit) { - $sql_base = " - SELECT + $sql = <<execQuery($sql, $parametersArray); - - if (isError($count)) - return $count; - - $count = ceil(current(getData($count))->count/$limit); - $sql = " - SELECT * FROM ( - " . $sql_base . " - ) a - ORDER BY insertamum DESC - LIMIT ? - OFFSET ? - "; + (COALESCE(ps.titelpre,'') || ' ' || COALESCE(ps.vorname,'') || ' ' || COALESCE(ps.nachname,'') || ' ' || COALESCE(ps.titelpost,'')) as sender, + (COALESCE(pr.titelpre,'') || ' ' || COALESCE(pr.vorname,'') || ' ' || COALESCE(pr.nachname,'') || ' ' || COALESCE(pr.titelpost,'')) as recipient, + fm.sender_id, + fm.recipient_id, + ms.status, + ms.insertamum as statusdatum + from + filtered_messages fm + join + public.tbl_msg_message m on fm.message_id = m.message_id + join + lastmsgstatus ms on fm.message_id = ms.message_id and fm.recipient_id = ms.person_id + left join + public.tbl_person ps on ps.person_id = fm.sender_id + left join + public.tbl_person pr on pr.person_id = fm.recipient_id + order by + m.insertamum DESC + limit ? + offset ?; +EOSQL; $parametersArray = array($person_id, $person_id, $limit, $offset); + $count = 0; $data = $this->execQuery($sql, $parametersArray); if (isError($data)) return $data; $data = getData($data); + if($data) + { + $count = ceil($data[0]->total_msgs / $limit); + } return success(['data' => $data, 'count' => $count]); } diff --git a/cis/testtool/frage.php b/cis/testtool/frage.php index bf2ee24c5..a5f4100c9 100644 --- a/cis/testtool/frage.php +++ b/cis/testtool/frage.php @@ -581,14 +581,14 @@ if($frage->frage_id!='') else $value=$p->t('testtool/blaettern').' >>'; - echo " $value"; + echo "$value"; } else { if(!$demo) { //Wenns der letzte Eintrag ist, wieder zum ersten springen - echo " ".$p->t('testtool/blaettern')." >>"; + echo "".$p->t('testtool/blaettern')." >>"; } } } diff --git a/cis/testtool/login.php b/cis/testtool/login.php index 182506ac3..cfc1ba63b 100644 --- a/cis/testtool/login.php +++ b/cis/testtool/login.php @@ -340,13 +340,26 @@ else } } -if ((isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) && - !isset($_SESSION['confirmation_needed']) && !isset($_SESSION['confirmed_code'])) || - (isset($_SESSION['confirmation_needed']) && $_SESSION['confirmation_needed'] === true && - isset($_SESSION['confirmed_code']) && $_SESSION['confirmed_code'] === true && - isset($_SESSION['externe_ueberwachung']) && $_SESSION['externe_ueberwachung'] === true && - isset($_SESSION['externe_ueberwachung_verified']) && $_SESSION['externe_ueberwachung_verified'] === true && - isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']))) +if ( + ( + isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) && + !isset($_SESSION['confirmation_needed']) && !isset($_SESSION['confirmed_code']) && + !isset($_SESSION['externe_ueberwachung']) && !isset($_SESSION['externe_ueberwachung_verified']) + ) + || + ( + isset($_SESSION['confirmation_needed']) && $_SESSION['confirmation_needed'] === true && + isset($_SESSION['confirmed_code']) && $_SESSION['confirmed_code'] === true && + isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) + ) + || + ( + isset($_SESSION['externe_ueberwachung']) && $_SESSION['externe_ueberwachung'] === true && + isset($_SESSION['externe_ueberwachung_verified']) && $_SESSION['externe_ueberwachung_verified'] === true && + isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) + ) + +) { $pruefling = new pruefling(); diff --git a/include/gebiet.class.php b/include/gebiet.class.php index a4df72338..fd2bfd198 100644 --- a/include/gebiet.class.php +++ b/include/gebiet.class.php @@ -345,6 +345,7 @@ class gebiet extends basis_db } //Pruefen ob jede Fragen mindestens 2 Vorschlaege hat + //Angepasst am 28.01.2026 auf ein Warning. $qry = "SELECT frage_id, nummer FROM testtool.tbl_frage WHERE (SELECT count(*) as anzahl FROM testtool.tbl_vorschlag WHERE frage_id=tbl_frage.frage_id)<2 AND gebiet_id=".$this->db_add_param($gebiet_id, FHC_INTEGER)." AND NOT demo;"; @@ -352,7 +353,7 @@ class gebiet extends basis_db { while($row = $this->db_fetch_object()) { - $this->errormsg .= "Frage Nummer $row->nummer (ID: $row->frage_id) hat weniger als 2 Vorschlaege.\n"; + $this->warningmsg .= "Frage Nummer $row->nummer (ID: $row->frage_id) hat weniger als 2 Vorschlaege.\n"; } } @@ -448,6 +449,52 @@ class gebiet extends basis_db } } + //Pruefen ob es leere Fragen (ohne Text, Bild oder Audio) gibt + $qry = "SELECT + fr.frage_id, + fr.nummer, + fs.sprache + FROM + testtool.tbl_frage fr + JOIN testtool.tbl_frage_sprache fs + USING (frage_id) + WHERE + (fs.text IS NULL + OR fs.text = '') + AND fs.bild IS NULL + AND fs.audio IS NULL + AND demo = false + AND gebiet_id=".$this->db_add_param($gebiet_id, FHC_INTEGER)." + AND EXISTS ( + SELECT + 1 + FROM + testtool.tbl_frage fr2 + JOIN testtool.tbl_frage_sprache fs2 + USING (frage_id) + WHERE + fs2.sprache = fs.sprache + AND fr2.gebiet_id = fr.gebiet_id + AND fr2.frage_id != fr.frage_id + AND ( + (fs2.text IS NOT NULL + AND fs2.text != '') + OR fs2.bild IS NOT NULL + OR fs2.audio IS NOT NULL + ) + AND demo = false + ) + ORDER BY + fs.sprache, + fr.nummer;"; + if($this->db_query($qry)) + { + while($row = $this->db_fetch_object()) + { + $this->warningmsg .= "Frage Nummer $row->nummer (ID: $row->frage_id), Sprache $row->sprache hat keinen Text, Bild oder Audio.\n"; + } + } + if($this->errormsg=='') return true; else diff --git a/public/css/tags.css b/public/css/tags.css index 9e0d7ee4b..e92f415b2 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -51,6 +51,14 @@ background-color: #6d4c41; } +.tag_dark_grey { + background-color: #595959; +} + +.tag_light_grey { + background-color: #9a9a9a; +} + .tag_blau { background-color: #508498; } diff --git a/public/js/api/factory/studiengang.js b/public/js/api/factory/studiengang.js index 12322cb3a..6d5ae15aa 100644 --- a/public/js/api/factory/studiengang.js +++ b/public/js/api/factory/studiengang.js @@ -16,10 +16,17 @@ */ export default { - getAllStudiensemesterAndAktOrNext() { + studiengangInformation() { return { method: 'get', - url: '/api/frontend/v1/Studiensemester/getStudiengangInfo' + url: '/api/frontend/v1/Studgang/getStudiengangInfo' }; }, + getStudiengangByKz(studiengang_kz) { + return { + method: 'get', + url: '/api/frontend/v1/organisation/StudiengangEP/getStudiengangByKz', + params: { studiengang_kz } + }; + } }; \ No newline at end of file diff --git a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js index 42952d0df..971783746 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js @@ -643,6 +643,24 @@ export const AbgabeMitarbeiterDetail = { 'projektarbeit'(newVal) { // set invertedFixtermin field for UI/UX purposes -> avoid double negation in text + // reset newTermin object + const typ = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === 'zwischen') + this.newTermin = { + 'paabgabe_id': -1, + 'projektarbeit_id': newVal.projektarbeit_id, + 'fixtermin': false, + 'invertedFixtermin': true, + 'kurzbz': '', + 'datum': new Date().toISOString().split('T')[0], + 'note': this.allowedNotenOptions.find(opt => opt.note == 9), + 'beurteilungsnotiz': '', + 'upload_allowed': typ.upload_allowed_default, + 'paabgabetyp_kurzbz': '', + 'bezeichnung': typ, + 'abgabedatum': null, + 'insertvon': this.viewData?.uid ?? '' + } + newVal?.abgabetermine?.forEach(termin => termin.invertedFixtermin = !termin.fixtermin) // default select german if projektarbeit sprache was null @@ -711,6 +729,7 @@ export const AbgabeMitarbeiterDetail = { v-model="newTermin.bezeichnung" :options="getAllowedAbgabeTypeOptions" :optionLabel="getOptionLabelAbgabetyp" + :optionDisabled="getOptionDisabled" scrollHeight="300px"> diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index 7fa78f7d1..34ddd3fc2 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -7,6 +7,7 @@ import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiStudiensemester from '../../../api/factory/studiensemester.js'; import AbgabeterminStatusLegende from "./StatusLegende.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js"; +import { splitMailsHelper } from "../../../helpers/EmailHelpers.js" // spoofed date testing // const todayISO = '2025-08-08' @@ -226,18 +227,17 @@ export const AbgabetoolAssistenz = { ]}; }, methods: { - sammelMailStudent() { + sammelMailStudent(param) { + const emails = this.selectedData .map(row => `${row.student_uid}@${this.domain}`) .join(','); - + const uniqueRecipients = [...new Set(emails)]; const subject = this.$p.t('abgabetool/c4sammelmailStudentBetreff', [this.selectedStudiengangOption?.bezeichnung]); - - const href = `mailto:${emails}?subject=${subject}`; - - window.location.href = href + splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p) }, - sammelMailBetreuer() { + sammelMailBetreuer(param) { + const recipientList = []; this.selectedData.forEach(row => { if (row.betreuer_mail) recipientList.push(row.betreuer_mail); @@ -246,11 +246,8 @@ export const AbgabetoolAssistenz = { // actually not necessary for email clients but looks better for assistenz if we avoid duplicates here const uniqueRecipients = [...new Set(recipientList)]; - const subject = this.$p.t('abgabetool/c4sammelmailBetreuerBetreff', [this.selectedStudiengangOption?.bezeichnung]); - const href = `mailto:${uniqueRecipients.join(',')}?subject=${encodeURIComponent(subject)}`; - - window.location.href = href; + splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p) }, selectHandler(e, cell) { const row = cell.getRow(); @@ -969,7 +966,10 @@ export const AbgabetoolAssistenz = { // this.loadProjektarbeiten() this.calcMaxTableHeight() - } + }, + getOptionDisabled(option) { + return !option.aktiv + }, }, computed: { emailItems() { @@ -1179,7 +1179,8 @@ export const AbgabetoolAssistenz = { :style="{'width': '100%'}" v-model="serienTermin.bezeichnung" :options="abgabeTypeOptions" - :optionLabel="getOptionLabelAbgabetyp"> + :optionLabel="getOptionLabelAbgabetyp" + :optionDisabled="getOptionDisabled"> @@ -1392,7 +1393,7 @@ export const AbgabetoolAssistenz = { > - + diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js index 67e1d09af..f33333ea3 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js @@ -304,7 +304,7 @@ export const AbgabetoolMitarbeiter = { pa.isCurrent = res.data[1] let paIsBenotet = false - if(pa.note !== undefined && pa !== null) { + if(pa.note !== undefined && pa.note !== null) { // check if the note is not defined as a non final projektarbeit note const opt = this.notenOptionsNonFinal.find(opt => opt.note) // if thats the case allow further work @@ -333,7 +333,6 @@ export const AbgabetoolMitarbeiter = { pa.student = `${pa.vorname} ${pa.nachname}` this.selectedProjektarbeit = pa - this.$refs.modalContainerAbgabeDetail.show() }).finally(()=>{this.loading = false}) diff --git a/public/js/components/Messages/Details/TableMessages.js b/public/js/components/Messages/Details/TableMessages.js index a55ddec63..6a4cf5ca0 100644 --- a/public/js/components/Messages/Details/TableMessages.js +++ b/public/js/components/Messages/Details/TableMessages.js @@ -243,6 +243,7 @@ export default { title: this.$p.t('global', 'aktionen') }); */ + this.$emit('tabulator_tablebuilt'); } }, { diff --git a/public/js/components/Messages/Messages.js b/public/js/components/Messages/Messages.js index 1f9afcb9e..e1fb69dc3 100644 --- a/public/js/components/Messages/Messages.js +++ b/public/js/components/Messages/Messages.js @@ -56,6 +56,7 @@ export default { }, data() { return { + tablebuilt: false, isVisibleDiv: false, messageId: null } @@ -139,8 +140,10 @@ export default { }, resetMessageId(){ this.messageId = null; + }, + tableBuilt: function() { + this.tablebuilt = true; } - }, template: `
@@ -155,6 +158,7 @@ export default { -
+
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js b/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js index bd7554a47..43995b918 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js @@ -1,3 +1,4 @@ +import { splitMailsHelper } from "../../../../helpers/EmailHelpers.js" export default { name: "Kontaktieren", computed: { @@ -22,60 +23,16 @@ export default { }, methods: { - async splitMails(mails, event) { - let splititem = ","; - let maillist = mails.join(splititem); - let mailto = ""; - - if (maillist.length > 2024) - { - if (await this.$fhcAlert.confirm({message: this.$p.t('stv', 'zuvieleEMails') }) === false) - return; - } - - let firstrun = true; - let useBcc = event?.ctrlKey || event?.metaKey; - while (maillist.length > 0) - { - if (maillist.length > 2024) - { - let splitposition = maillist.lastIndexOf(splititem, 1900); - mailto = maillist.substring(0, splitposition); - maillist = maillist.substring(splitposition + 1); - } - else - { - mailto = maillist; - maillist = ""; - } - - let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`; - - if (firstrun) - { - window.location.href = mailLink; - firstrun = false; - } - else - { - if (await this.$fhcAlert.confirm({message: this.$p.t('stv', 'weitereEMail')}) === true) - { - window.location.href = mailLink; - } - } - - } - }, internMail(event) { if (this.internMails.length) { - this.splitMails(this.internMails, event); + splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p) } }, privateMail(event) { if (this.privateMails.length) { - this.splitMails(this.privateMails, event); + splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p) } } }, diff --git a/public/js/helpers/EmailHelpers.js b/public/js/helpers/EmailHelpers.js new file mode 100644 index 000000000..87daa828a --- /dev/null +++ b/public/js/helpers/EmailHelpers.js @@ -0,0 +1,45 @@ +export async function splitMailsHelper(mails, event, subject, alertPluginRef, phrasenPluginRef) { + let splititem = ","; + let maillist = mails.join(splititem); + let mailto = ""; + // take subject line length + '?subject=' length into account + const subjectlength = subject && typeof subject === 'string' ? subject.length + 9 : 0 + if (maillist.length > 2024) + { + if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'zuvieleEMails') }) === false) + return; + } + + let firstrun = true; + let useBcc = event?.ctrlKey || event?.metaKey; + while (maillist.length > 0) + { + if (maillist.length + subjectlength > 2024) + { + let splitposition = maillist.lastIndexOf(splititem, 1900); + mailto = maillist.substring(0, splitposition); + maillist = maillist.substring(splitposition + 1); + } + else + { + mailto = maillist; + maillist = ""; + } + + let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`; + if(subject && typeof subject === 'string') mailLink += `?subject=${subject}` + if (firstrun) + { + window.location.href = mailLink; + firstrun = false; + } + else + { + if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'weitereEMail')}) === true) + { + window.location.href = mailLink; + } + } + + } +} \ No newline at end of file diff --git a/public/js/helpers/TagHelper.js b/public/js/helpers/TagHelper.js new file mode 100644 index 000000000..9282aa167 --- /dev/null +++ b/public/js/helpers/TagHelper.js @@ -0,0 +1,124 @@ +export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags") +{ + if (!addedTag || !Array.isArray(addedTag.response)) + return; + + rows.forEach(row => + { + const rowData = row.getData(); + let updated = false; + + addedTag.response.forEach(tag => + { + if (rowData[matchKey] !== tag[matchKey]) + return; + + let tags; + try { + tags = JSON.parse(rowData[tagsKey] || "[]"); + } catch (e) { + tags = []; + } + + if (!Array.isArray(tags)) + tags = []; + + if (tags.some(t => t?.id === tag.id)) + return; + + let newTag = { ...addedTag, id: tag.id }; + + tags.unshift(newTag); + + rowData[tagsKey] = JSON.stringify(tags); + updated = true; + }); + + if (updated) + row.update(rowData); + }); +} + +export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags']) +{ + if (!Array.isArray(tagsKeys)) + tagsKeys = [tagsKeys]; + + rows.forEach(row => { + let rowData = row.getData(); + let updates = {}; + let changed = false; + + tagsKeys.forEach(key => { + let tags; + + try { + tags = JSON.parse(rowData[key] || "[]"); + } catch (e) { + tags = []; + } + + if (!Array.isArray(tags)) + return; + + let filtered = tags.filter(tag => tag?.id !== deletedTag); + + if (filtered.length !== tags.length) + { + updates[key] = JSON.stringify(filtered); + changed = true; + } + }); + + if (changed) { + row.update(updates); + row.reformat(); + } + }); +} + + +export function updateTagInTable(updatedTag, rows, fields = ['tags']) +{ + if (!Array.isArray(fields)) + fields = [fields]; + + rows.forEach(row => + { + const rowData = row.getData(); + let updated = false; + + fields.forEach(field => + { + if (!rowData[field]) + return; + + let fieldData; + try { + fieldData = JSON.parse(rowData[field] || "[]"); + } catch (e) { + return; + } + + if (!Array.isArray(fieldData)) + return; + + let index = fieldData.findIndex(tag => tag?.id === updatedTag.id); + + if (index !== -1) + { + fieldData[index] = { ...updatedTag }; + let updatedFieldData = JSON.stringify(fieldData); + + if (updatedFieldData !== rowData[field]) + { + rowData[field] = updatedFieldData; + updated = true; + } + } + }); + + if (updated) + row.update(rowData); + }); +} diff --git a/public/js/tabulator/filters/extendedHeaderFilter.js b/public/js/tabulator/filters/extendedHeaderFilter.js index 7bf86c119..35b66dc1c 100644 --- a/public/js/tabulator/filters/extendedHeaderFilter.js +++ b/public/js/tabulator/filters/extendedHeaderFilter.js @@ -146,7 +146,7 @@ export function tagHeaderFilter(headerValue, rowValue, rowData, filterParams) if (Array.isArray(data)) { combinedText = data - .filter(item => item?.done === false) + .filter(item => item?.done !== true) .map(item => `${item?.beschreibung} ${item?.notiz}`) .join(' '); } diff --git a/public/js/tabulator/formatter/tags.js b/public/js/tabulator/formatter/tags.js new file mode 100644 index 000000000..0d2f5004c --- /dev/null +++ b/public/js/tabulator/formatter/tags.js @@ -0,0 +1,67 @@ +export function tagFormatter(cell, tagComponent) +{ + let tags = cell.getValue(); + if (!tags) return; + + let container = document.createElement('div'); + container.className = "d-flex gap-1"; + + let parsedTags = JSON.parse(tags); + let maxVisibleTags = 2; + + const rowData = cell.getRow().getData(); + if (rowData._tagExpanded === undefined) { + rowData._tagExpanded = false; + } + + const renderTags = () => { + container.innerHTML = ''; + parsedTags = parsedTags.filter(item => item !== null); + + parsedTags.sort((a, b) => { + let adone = a.done ? 1 : 0; + let bbone = b.done ? 1 : 0; + + if (adone !== bbone) + { + return adone - bbone; + } + return b.id - a.id; + }); + 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; + tagElement.className = "tag " + tag.style; + if (tag.done) tagElement.className += " tag_done"; + + tagElement.addEventListener('click', (event) => { + event.stopPropagation(); + event.preventDefault(); + tagComponent.editTag(tag.id); + }); + + container.appendChild(tagElement); + }); + + 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"; + + toggle.addEventListener('click', () => { + rowData._tagExpanded = !rowData._tagExpanded; + renderTags(); + }); + + container.appendChild(toggle); + } + }; + + renderTags(); + return container; +} \ No newline at end of file diff --git a/skin/images/sancho/sancho_footer_lvevaluierung.jpg b/skin/images/sancho/sancho_footer_lvevaluierung.jpg new file mode 100644 index 000000000..4c72b26c6 Binary files /dev/null and b/skin/images/sancho/sancho_footer_lvevaluierung.jpg differ diff --git a/skin/images/sancho/sancho_header_lvevaluierung.jpg b/skin/images/sancho/sancho_header_lvevaluierung.jpg new file mode 100644 index 000000000..51284645d Binary files /dev/null and b/skin/images/sancho/sancho_header_lvevaluierung.jpg differ diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php index 793930243..4ddb38203 100644 --- a/system/dbupdate_3.4.php +++ b/system/dbupdate_3.4.php @@ -91,6 +91,7 @@ require_once('dbupdate_3.4/69065_Projektarbeiten_Firmen_verwalten.php'); require_once('dbupdate_3.4/68744_StV_settings.php'); require_once('dbupdate_3.4/62889_reihungstest_ueberwachung_mit_constructor.php'); require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php'); +require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php'); // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen echo '

Pruefe Tabellen und Attribute!

'; diff --git a/system/dbupdate_3.4/71645_studvw_messagetab_ladezeit.php b/system/dbupdate_3.4/71645_studvw_messagetab_ladezeit.php new file mode 100644 index 000000000..4ad88fba9 --- /dev/null +++ b/system/dbupdate_3.4/71645_studvw_messagetab_ladezeit.php @@ -0,0 +1,28 @@ +db_query("SELECT * FROM pg_class WHERE relname='idx_tbl_msg_message_person_id'")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_msg_message_person_id ON public.tbl_msg_message USING btree (person_id)"; + + if (! $db->db_query($qry)) + echo 'idx_tbl_msg_message_person_id: ' . $db->db_last_error() . '
'; + else + echo 'Index idx_tbl_msg_message_person_id angelegt
'; + } +} + +if ($result = $db->db_query("SELECT * FROM pg_class WHERE relname='idx_tbl_msg_recipient_person_id'")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_msg_recipient_person_id ON public.tbl_msg_recipient USING btree (person_id)"; + + if (! $db->db_query($qry)) + echo 'idx_tbl_msg_recipient_person_id: ' . $db->db_last_error() . '
'; + else + echo 'Index idx_tbl_msg_recipient_person_id angelegt
'; + } +} diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 5dc9bc1c0..deb856663 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -19087,6 +19087,27 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'kvp', + 'phrase' => 'error.opproject_does_not_exists', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Es ist kein Openproject Projekt verknüpft.", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "No Openproject project is linked.", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + //******************* KVP end array( 'app' => 'international', 'category' => 'international',