diff --git a/public/css/components/abgabetool/abgabe.css b/public/css/components/abgabetool/abgabe.css index b1d8c2507..7c806ce2f 100644 --- a/public/css/components/abgabetool/abgabe.css +++ b/public/css/components/abgabetool/abgabe.css @@ -193,4 +193,28 @@ .p-accordion .p-accordion-header .p-accordion-header-link { padding: 12px!important; +} + +/* 1. Stick the Header */ +#abgabetable .tabulator-header .tabulator-col.sticky-col { + position: sticky; + left: 0; + z-index: 10; /* Must be higher than other headers */ + background-color: #fff; /* Opaque background is required */ + border-right: 2px solid #ddd; /* Optional: Separator line */ +} + +/* 2. Stick the Data Cells */ +#abgabetable .tabulator-tableholder .tabulator-row .tabulator-cell.sticky-col { + position: sticky; + left: 0; + z-index: 10; /* Ensure it floats above other cells */ + background-color: #fff; /* Match your row background color */ + border-right: 2px solid #ddd; /* Optional: Separator line */ +} + +/* 3. Fix for Hover Effects (Optional) */ +/* If you use hover rows, you need to ensure the sticky cell matches the hover color */ +#abgabetable .tabulator-row:hover .tabulator-cell.sticky-col { + background-color: #ccc; /* Match your existing hover color */ } \ No newline at end of file diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index 7c6dec714..67a5b1727 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -57,6 +57,7 @@ export const AbgabetoolAssistenz = { }, data() { return { + tableData: null, studiensemesterOptions: null, allSem: null, curSem: null, @@ -113,18 +114,71 @@ export const AbgabetoolAssistenz = { renderVerticalBuffer: 2000, columns: [ { - field: 'rowSelection', - formatter: 'rowSelection', - titleFormatter: 'rowSelection', - titleFormatterParams: { - rowRange: "active" // Only toggle the values of the active filtered rows + formatter: function (cell, formatterParams, onRendered) { + // create the built-in checkbox + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + + // Handle select manually + checkbox.addEventListener("click", (e) => { + e.stopPropagation(); + + // call our function + if (formatterParams && formatterParams.handleClick) { + formatterParams.handleClick(e, cell); + } + }); + + cell.getRow().getData().checkbox = checkbox + + let wrapper = document.createElement("div"); + wrapper.style.cssText = "display: flex; justify-content: center; align-items: center; height: 100%; width: 100%;"; + + wrapper.appendChild(checkbox); + + return wrapper; }, - hozAlign:"center", + titleFormatter: function (cell, formatterParams, onRendered) { + // create the built-in checkbox + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + + // Handle "select all" manually + checkbox.addEventListener("click", (e) => { + e.stopPropagation(); + + // call our function + if (formatterParams && formatterParams.handleClick) { + formatterParams.handleClick(e, cell); + } + }); + + return checkbox; + }, + hozAlign: "center", headerSort: false, - frozen: true, - width: 40 + formatterParams: { + handleClick: this.selectHandler + }, + titleFormatterParams: { + handleClick: this.selectAllHandler + }, + width: 50, + cssClass: 'sticky-col' }, - {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, tooltip:false, minWidth: 150}, + // { + // field: 'rowSelection', + // formatter: 'rowSelection', + // titleFormatter: 'rowSelection', + // titleFormatterParams: { + // rowRange: "active" // Only toggle the values of the active filtered rows + // }, + // hozAlign:"center", + // headerSort: false, + // frozen: true, + // width: 40 + // }, + {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, tooltip:false, minWidth: 150, cssClass: 'sticky-col'}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'student_vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'student_nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1}, @@ -150,12 +204,51 @@ export const AbgabetoolAssistenz = { { event: "rowSelectionChanged", handler: async(data) => { + this.selectedData.filter(sd => !data.includes(sd)).forEach(fsd => { + if(fsd.checkbox) fsd.checkbox.checked = false + }) + + data.forEach(d => { + if(d.checkbox) d.checkbox.checked = true + }) + this.selectedData = data } } ]}; }, methods: { + selectHandler(e, cell) { + const row = cell.getRow(); + + if(row.isSelected()){ + row.deselect(); + } else { + row.select(); + } + + // stop built-in handler + e.stopPropagation(); + return false; + }, + selectAllHandler(e, cell) { + const table = cell.getTable(); + const rows = table.getRows(); + + // custom select all logic + const allowed = rows.filter(r => r.getData().selectable); + const selected = allowed.every(r => r.isSelected()); + + if(selected){ + allowed.forEach(r => r.deselect()); + } else { + allowed.forEach(r => r.select()); + } + + // stop built-in handler + e.stopPropagation(); + return false; + }, checkQualityGateStatus(projekt) { // TODO: might refine the representation of these states and maybe refactor code a little const qgate1Termine = [] @@ -534,7 +627,7 @@ export const AbgabetoolAssistenz = { mapProjekteToTableData(projekte) { // const now = luxon.DateTime.now(); return projekte.map(projekt => { - + projekt.prevTermin = null; projekt.nextTermin = null; @@ -753,12 +846,11 @@ export const AbgabetoolAssistenz = { this.projektarbeiten = data[0] this.domain = data[1] - const mappedData = this.mapProjekteToTableData(this.projektarbeiten) + this.tableData = this.mapProjekteToTableData(this.projektarbeiten) await this.tableBuiltPromise - // this.$refs.abgabeTable.tabulator.clearData() - this.$refs.abgabeTable.tabulator.setData(mappedData); + this.$refs.abgabeTable.tabulator.setData(this.tableData); }, loadProjektarbeiten(all = false, callback) { this.loading = true @@ -865,8 +957,7 @@ export const AbgabetoolAssistenz = { }).catch(e => { this.loading = false }) - - + // fetch noten options //TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API this.$api.call(ApiAbgabe.getNoten()).then(res => { @@ -1109,7 +1200,7 @@ export const AbgabetoolAssistenz = { -
+

{{$p.t('abgabetool/abgabetoolTitle')}}

diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js index 0e24a496c..a934230ca 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js @@ -38,6 +38,7 @@ export const AbgabetoolMitarbeiter = { }, data() { return { + tableData: null, abgabetypenBetreuer: null, detailIsFullscreen: false, phrasenPromise: null, @@ -79,17 +80,60 @@ export const AbgabetoolMitarbeiter = { rowHeight: 80, columns: [ { - formatter: 'rowSelection', - titleFormatter: 'rowSelection', - titleFormatterParams: { - rowRange: "active" // Only toggle the values of the active filtered rows + formatter: function (cell, formatterParams, onRendered) { + // create the built-in checkbox + if(!cell.getRow().getData().selectable) return + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + + // Handle select manually + checkbox.addEventListener("click", (e) => { + e.stopPropagation(); + + // call our function + if (formatterParams && formatterParams.handleClick) { + formatterParams.handleClick(e, cell); + } + }); + + cell.getRow().getData().checkbox = checkbox + + let wrapper = document.createElement("div"); + wrapper.style.cssText = "display: flex; justify-content: center; align-items: center; height: 100%; width: 100%;"; + + wrapper.appendChild(checkbox); + + return wrapper; }, - hozAlign:"center", + titleFormatter: function (cell, formatterParams, onRendered) { + // create the built-in checkbox + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + + // Handle "select all" manually + checkbox.addEventListener("click", (e) => { + e.stopPropagation(); + + // call our function + if (formatterParams && formatterParams.handleClick) { + formatterParams.handleClick(e, cell); + } + }); + + return checkbox; + }, + hozAlign: "center", headerSort: false, - frozen: true, - width: 70 + formatterParams: { + handleClick: this.selectHandler + }, + titleFormatterParams: { + handleClick: this.selectAllHandler + }, + width: 50, + cssClass: 'sticky-col' }, - {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false}, + {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false, cssClass: 'sticky-col'}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4kontakt'))), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false, visible: false}, {title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1}, @@ -122,12 +166,51 @@ export const AbgabetoolMitarbeiter = { { event: "rowSelectionChanged", handler: async(data) => { + this.selectedData.filter(sd => !data.includes(sd)).forEach(fsd => { + if(fsd.checkbox) fsd.checkbox.checked = false + }) + + data.forEach(d => { + if(d.checkbox) d.checkbox.checked = true + }) + this.selectedData = data } } ]}; }, methods: { + selectHandler(e, cell) { + const row = cell.getRow(); + + if(row.isSelected()) { + row.deselect(); + } else { + row.select(); + } + + // stop built-in handler + e.stopPropagation(); + return false; + }, + selectAllHandler(e, cell) { + const table = cell.getTable(); + const rows = table.getRows(); + + // custom select all logic + const allowed = rows.filter(r => r.getData().selectable); + const selected = allowed.every(r => r.isSelected()); + + if(selected) { + allowed.forEach(r => r.deselect()); + } else { + allowed.forEach(r => r.select()); + } + + // stop built-in handler + e.stopPropagation(); + return false; + }, handleToggleFullscreenDetail() { this.detailIsFullscreen = !this.detailIsFullscreen }, @@ -286,7 +369,9 @@ export const AbgabetoolMitarbeiter = { this.projektarbeiten = data[0] this.domain = data[1] - const d = data[0]?.retval?.map(projekt => { + this.tableData = data[0]?.retval?.map(projekt => { + + projekt.selectable = projekt.betreuerart_kurzbz !== 'Zweitbegutachter' return { ...projekt, @@ -305,7 +390,7 @@ export const AbgabetoolMitarbeiter = { }) this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns) - this.$refs.abgabeTable.tabulator.setData(d); + this.$refs.abgabeTable.tabulator.setData(this.tableData); }, loadProjektarbeiten(all = false, callback) { this.$api.call(ApiAbgabe.getMitarbeiterProjektarbeiten(all)) @@ -477,7 +562,7 @@ export const AbgabetoolMitarbeiter = { -
+

{{$p.t('abgabetool/abgabetoolTitle')}}