diff --git a/public/js/components/LVVerwaltung/Setup/Table.js b/public/js/components/LVVerwaltung/Setup/Table.js index 830cfc9ea..67c63c3bb 100644 --- a/public/js/components/LVVerwaltung/Setup/Table.js +++ b/public/js/components/LVVerwaltung/Setup/Table.js @@ -4,6 +4,8 @@ import DetailsForm from "../Details/Form.js"; import CoreTag from '../../Tag/Tag.js'; import { tagHeaderFilter } from '../../../../js/tabulator/filters/extendedHeaderFilter.js'; import { extendedHeaderFilter } from "../../../../js/tabulator/filters/extendedHeaderFilter.js"; +import { tagFormatter } from "../../../../js/tabulator/formatter/tags.js"; +import { addTagInTable, deleteTagInTable, updateTagInTable } from "../../../../js/helpers/TagHelper.js"; import ApiLv from "../../../api/lehrveranstaltung.js"; import ApiTag from "../../../api/lehrveranstaltung/tag.js"; @@ -223,72 +225,7 @@ export default { headerFilter: "input", headerFilterFunc: tagHeaderFilter, headerFilterFuncParams: {field: 'tags'}, - formatter: (cell) => { - 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(); - this.$refs.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; - }, + formatter: (cell) => tagFormatter(cell, this.$refs.tagComponent), width: 150, }, { @@ -444,104 +381,16 @@ export default { }, addedTag(addedTag) { - const table = this.$refs.table.tabulator; - - this.selectedRows.forEach(row => - { - if (Array.isArray(addedTag.response)) - { - addedTag.response.forEach(tag => { - const targetRow = this.allRows.find(row => row.getData().lehreinheit_id === tag.lehreinheit_id); - if (targetRow) - { - const rowData = targetRow.getData(); - let tags = []; - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - const tagExists = tags.some((t) => t.id === tag.id); - if (!tagExists) - { - addedTag.id = tag.id; - tags.unshift({ ...addedTag }); - targetRow.update({ tags: JSON.stringify(tags) }); - targetRow.reformat(); - } - } - }); - } - }); + addTagInTable(addedTag, this.allRows, 'lehreinheit_id') }, - deletedTag(deletedTag) { - const targetRow = this.allRows.find(row => { - const rowData = row.getData(); - - let tags = []; - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - return tags.some(tag => tag.id === deletedTag); - }); - - if (targetRow) { - const rowData = targetRow.getData(); - let tags = []; - - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - const filteredTags = tags.filter(t => t.id !== deletedTag); - const updatedTags = JSON.stringify(filteredTags); - - if (updatedTags !== rowData.tags) { - targetRow.update({ - tags: updatedTags - }); - - targetRow.reformat(); - } - } + deletedTag(deletedTag) + { + deleteTagInTable(deletedTag, this.allRows); }, - updatedTag(updatedTag) { - const targetRow = this.allRows.find(row => { - const rowData = row.getData(); - let tags = []; - - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - return tags.some(t => t?.id === updatedTag.id); - }); - - if (targetRow) - { - const rowData = targetRow.getData(); - let tags = []; - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - let changed = false; - - const tagIndex = tags.findIndex(tag => tag?.id === updatedTag.id); - if (tagIndex !== -1) { - tags[tagIndex] = { ...updatedTag }; - changed = true; - } - - if (changed) - { - targetRow.update({ - tags: JSON.stringify(tags), - }); - targetRow.reformat(); - } - } + updatedTag(updatedTag) + { + updateTagInTable(updatedTag, this.allRows); }, async copyLehreinheit(row, art) { diff --git a/public/js/components/Stv/Studentenverwaltung/List.js b/public/js/components/Stv/Studentenverwaltung/List.js index 6f06787a1..0ba1cd5ee 100644 --- a/public/js/components/Stv/Studentenverwaltung/List.js +++ b/public/js/components/Stv/Studentenverwaltung/List.js @@ -2,6 +2,8 @@ import {CoreFilterCmpt} from "../../filter/Filter.js"; import ListNew from './List/New.js'; import CoreTag from '../../Tag/Tag.js'; import { tagHeaderFilter } from "../../../tabulator/filters/extendedHeaderFilter.js"; +import { addTagInTable, deleteTagInTable, updateTagInTable } from "../../../../js/helpers/TagHandler.js"; +import { tagFormatter } from "../../../../js/tabulator/formatter/tags.js"; import { extendedHeaderFilter } from "../../../tabulator/filters/extendedHeaderFilter.js"; import ApiTag from "../../../api/factory/stv/tag.js"; import ListFilter from './List/Filter.js'; @@ -67,72 +69,7 @@ export default { headerFilter: "input", headerFilterFunc: tagHeaderFilter, headerFilterFuncParams: {field: 'tags'}, - formatter: (cell) => { - 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(); - this.$refs.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; - }, + formatter: (cell) => tagFormatter(cell, this.$refs.tagComponent), width: 150, }, {title:"Nachname", field:"nachname", headerFilter: true}, @@ -235,7 +172,7 @@ export default { { event: 'dataProcessed', handler: (data) => { - this.reexpandRows() + this.getAllRows() this.autoSelectRows(data) } }, @@ -481,136 +418,18 @@ export default { //methods tags addedTag(addedTag) { - const table = this.$refs.table.tabulator; - - this.selectedRows.forEach(row => - { - if (Array.isArray(addedTag.response)) - { - addedTag.response.forEach(tag => { - const targetRow = this.allRows.find(row => row.getData().prestudent_id === tag.prestudent_id); - if (targetRow) - { - const rowData = targetRow.getData(); - let tags = []; - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - const tagExists = tags.some((t) => t.id === tag.id); - if (!tagExists) - { - addedTag.id = tag.id; - tags.unshift({ ...addedTag }); - targetRow.update({ tags: JSON.stringify(tags) }); - targetRow.reformat(); - } - } - }); - } - }); + addTagInTable(addedTag, this.allRows, 'prestudent_id') }, - deletedTag(deletedTag) { - const targetRow = this.allRows.find(row => { - const rowData = row.getData(); - - let tags = []; - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - return tags.some(tag => tag.id === deletedTag); - }); - - if (targetRow) { - const rowData = targetRow.getData(); - let tags = []; - - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - const filteredTags = tags.filter(t => t.id !== deletedTag); - const updatedTags = JSON.stringify(filteredTags); - - if (updatedTags !== rowData.tags) { - targetRow.update({ - tags: updatedTags - }); - - targetRow.reformat(); - } - } - }, - updatedTag(updatedTag) { - const targetRow = this.allRows.find(row => { - const rowData = row.getData(); - let tags = []; - - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - return tags.some(t => t?.id === updatedTag.id); - }); - - if (targetRow) - { - const rowData = targetRow.getData(); - let tags = []; - try { - tags = JSON.parse(rowData.tags || '[]'); - } catch (e) {} - - let changed = false; - - const tagIndex = tags.findIndex(tag => tag?.id === updatedTag.id); - if (tagIndex !== -1) { - tags[tagIndex] = { ...updatedTag }; - changed = true; - } - - if (changed) - { - targetRow.update({ - tags: JSON.stringify(tags), - }); - targetRow.reformat(); - } - } - }, - getAllRows(rows) + deletedTag(deletedTag) { - let result = []; - rows.forEach(row => - { - result.push(row); - let children = row.getTreeChildren(); - if(children && children.length > 0) - { - result = result.concat(this.getAllRows(children)); - } - }); - return result; + deleteTagInTable(deletedTag, this.allRows); }, - reexpandRows() { - this.allRows = this.getAllRows(this.$refs.table.tabulator.getRows()); - - const matchingRows = this.allRows.filter(row => - this.expanded.includes(row.getData().uniqueindex) - ); - - if (matchingRows.length === 0) - this.currentTreeLevel = 0; - - matchingRows.forEach((row, index) => { - row._row.modules.dataTree.open = true; - - if (index === matchingRows.length - 1) - { - row.treeExpand(); - } - }); + updatedTag(updatedTag) + { + updateTagInTable(updatedTag, this.allRows) + }, + getAllRows() { + this.allRows = this.$refs.table.tabulator.getRows(); }, }, 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/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