@@ -74,6 +74,16 @@ OCA.Analytics.Filter = {
7474 thresholdEl . classList . remove ( 'report-option-active' ) ;
7575 }
7676 }
77+
78+ const tableOptionsEl = document . getElementById ( 'optionsMenuTableOptions' ) ;
79+ if ( tableOptionsEl ) {
80+ const calculatedColumns = OCA . Analytics . currentReportData . options . tableoptions ?. calculatedColumns ;
81+ if ( typeof calculatedColumns === 'string' && calculatedColumns . trim ( ) !== '' ) {
82+ tableOptionsEl . classList . add ( 'report-option-active' ) ;
83+ } else {
84+ tableOptionsEl . classList . remove ( 'report-option-active' ) ;
85+ }
86+ }
7787 } ,
7888
7989 /**
@@ -756,6 +766,7 @@ OCA.Analytics.Filter = {
756766 OCA . Analytics . Filter . calculatedColumnsEditorState . draft = null ;
757767
758768 OCA . Analytics . Filter . renderCalculatedColumnsList ( ) ;
769+ OCA . Analytics . Filter . Drag . syncCalculatedColumns ( ) ;
759770 OCA . Analytics . Filter . closeCalculatedColumnEditor ( ) ;
760771 } ,
761772
@@ -1304,6 +1315,7 @@ OCA.Analytics.Filter = {
13041315 state . parseError = false ;
13051316 document . getElementById ( 'tableOptionsCalculatedColumns' ) . value = OCA . Analytics . Filter . serializeCalculatedColumnsValue ( state . items ) ;
13061317 OCA . Analytics . Filter . renderCalculatedColumnsList ( ) ;
1318+ OCA . Analytics . Filter . Drag . syncCalculatedColumns ( ) ;
13071319 OCA . Analytics . Filter . closeCalculatedColumnEditor ( ) ;
13081320 } ,
13091321
@@ -1313,10 +1325,12 @@ OCA.Analytics.Filter = {
13131325 return ;
13141326 }
13151327
1316- state . items . splice ( state . editingIndex , 1 ) ;
1328+ const deletedIndex = state . editingIndex ;
1329+ state . items . splice ( deletedIndex , 1 ) ;
13171330 state . parseError = false ;
13181331 document . getElementById ( 'tableOptionsCalculatedColumns' ) . value = OCA . Analytics . Filter . serializeCalculatedColumnsValue ( state . items ) ;
13191332 OCA . Analytics . Filter . renderCalculatedColumnsList ( ) ;
1333+ OCA . Analytics . Filter . Drag . syncCalculatedColumns ( deletedIndex ) ;
13201334 OCA . Analytics . Filter . closeCalculatedColumnEditor ( ) ;
13211335 } ,
13221336
@@ -1840,6 +1854,41 @@ OCA.Analytics.Filter = {
18401854} ;
18411855
18421856OCA . Analytics . Filter . Drag = {
1857+ createDraggableItem : function ( text , index , calculationIndex = null ) {
1858+ const div = document . createElement ( 'div' ) ;
1859+ div . id = 'column-' + index ;
1860+ div . className = 'draggable tableOptionsLayoutItem' ;
1861+ div . draggable = true ;
1862+ div . ondragstart = OCA . Analytics . Filter . Drag . drag ;
1863+ if ( calculationIndex !== null ) {
1864+ div . dataset . calculationIndex = calculationIndex ;
1865+ }
1866+
1867+ const iconDiv = document . createElement ( 'div' ) ;
1868+ iconDiv . className = 'icon-analytics-gripLines sidebarPointer' ;
1869+ div . appendChild ( iconDiv ) ;
1870+
1871+ const textSpan = document . createElement ( 'span' ) ;
1872+ textSpan . textContent = text ;
1873+ div . appendChild ( textSpan ) ;
1874+
1875+ return div ;
1876+ } ,
1877+
1878+ getCalculatedColumnItems : function ( ) {
1879+ const calculatedColumnsInput = document . getElementById ( 'tableOptionsCalculatedColumns' ) ;
1880+ if ( calculatedColumnsInput ) {
1881+ const parsed = OCA . Analytics . Filter . parseCalculatedColumnsValue ( calculatedColumnsInput . value ) ;
1882+ return parsed . items
1883+ . map ( calc => OCA . Analytics . Visualization . normalizeCalculatedColumn ( calc ) )
1884+ . filter ( calc => calc !== null && OCA . Analytics . Visualization . isCalculatedColumnRenderable ( calc ) ) ;
1885+ }
1886+
1887+ const tableOptions = OCA . Analytics . currentReportData . options . tableoptions || { } ;
1888+ return OCA . Analytics . Visualization . getCalculatedColumns ( tableOptions )
1889+ . filter ( calc => OCA . Analytics . Visualization . isCalculatedColumnRenderable ( calc ) ) ;
1890+ } ,
1891+
18431892 /**
18441893 * Allow dropping an element during drag and drop operations.
18451894 */
@@ -1892,56 +1941,102 @@ OCA.Analytics.Filter.Drag = {
18921941 */
18931942 initialize : function ( ) {
18941943 let layoutConfig = OCA . Analytics . currentReportData . options . tableoptions . layout ;
1895- let headerItems = OCA . Analytics . currentReportData . header ;
1944+ const sourceHeaders = OCA . Analytics . currentReportData . header || [ ] ;
1945+ const calculations = OCA . Analytics . Filter . Drag . getCalculatedColumnItems ( ) ;
1946+ const headerItems = [
1947+ ...sourceHeaders ,
1948+ ...calculations . map ( calc => calc . title || t ( 'analytics' , 'Calculated column' ) ) ,
1949+ ] ;
18961950 let rowsSection = document . getElementById ( 'rows' ) ;
18971951
18981952 // Clear existing sections
18991953 document . querySelectorAll ( '.section' ) . forEach ( function ( section ) {
19001954 section . innerHTML = '<p>' + section . id . charAt ( 0 ) . toUpperCase ( ) + section . id . slice ( 1 ) + '</p>' ;
19011955 } ) ;
19021956
1903- // Function to create a draggable div
1904- const createDraggableItem = ( text , index ) => {
1905- let div = document . createElement ( 'div' ) ;
1906- div . id = 'column-' + index ;
1907- div . className = 'draggable tableOptionsLayoutItem' ;
1908- div . draggable = true ;
1909- div . ondragstart = OCA . Analytics . Filter . Drag . drag ;
1910-
1911- // Create and append the icon div
1912- let iconDiv = document . createElement ( 'div' ) ;
1913- iconDiv . className = 'icon-analytics-gripLines sidebarPointer' ;
1914- div . appendChild ( iconDiv ) ;
1915-
1916- // Create and append the text span
1917- let textSpan = document . createElement ( 'span' ) ;
1918- textSpan . textContent = text ;
1919- div . appendChild ( textSpan ) ;
1920-
1921- return div ;
1922- } ;
1923-
19241957 if ( ! layoutConfig || Object . keys ( layoutConfig ) . length === 0 ) {
19251958 // Fallback: Add all header items to the "rows" section
19261959 headerItems . forEach ( ( text , index ) => {
1927- let draggableItem = createDraggableItem ( text , index ) ;
1960+ const calculationIndex = index >= sourceHeaders . length ? index - sourceHeaders . length : null ;
1961+ let draggableItem = OCA . Analytics . Filter . Drag . createDraggableItem ( text , index , calculationIndex ) ;
19281962 rowsSection . appendChild ( draggableItem ) ;
19291963 } ) ;
19301964 } else {
1965+ const placedIndexes = new Set ( ) ;
19311966 // Append items to their respective sections based on layoutConfig
19321967 Object . keys ( layoutConfig ) . forEach ( section => {
19331968 let sectionElement = document . getElementById ( section ) ;
19341969 layoutConfig [ section ] . forEach ( index => {
1935- let draggableItem = createDraggableItem ( headerItems [ index ] , index ) ;
1970+ if ( ! sectionElement || headerItems [ index ] === undefined || placedIndexes . has ( index ) ) {
1971+ return ;
1972+ }
1973+ const calculationIndex = index >= sourceHeaders . length ? index - sourceHeaders . length : null ;
1974+ let draggableItem = OCA . Analytics . Filter . Drag . createDraggableItem ( headerItems [ index ] , index , calculationIndex ) ;
19361975 sectionElement . appendChild ( draggableItem ) ;
1976+ placedIndexes . add ( index ) ;
19371977 } ) ;
19381978 } ) ;
1979+
1980+ headerItems . forEach ( ( text , index ) => {
1981+ if ( placedIndexes . has ( index ) ) {
1982+ return ;
1983+ }
1984+ const calculationIndex = index >= sourceHeaders . length ? index - sourceHeaders . length : null ;
1985+ rowsSection . appendChild ( OCA . Analytics . Filter . Drag . createDraggableItem ( text , index , calculationIndex ) ) ;
1986+ } ) ;
19391987 }
19401988
19411989 // Manage placeholders initially
19421990 OCA . Analytics . Filter . Drag . managePlaceholders ( ) ;
19431991 } ,
19441992
1993+ syncCalculatedColumns : function ( deletedIndex = null ) {
1994+ const rowsSection = document . getElementById ( 'rows' ) ;
1995+ if ( ! rowsSection ) {
1996+ return ;
1997+ }
1998+
1999+ const sourceColumnCount = ( OCA . Analytics . currentReportData . header || [ ] ) . length ;
2000+ if ( deletedIndex !== null ) {
2001+ document . querySelectorAll ( '.draggable[data-calculation-index]' ) . forEach ( item => {
2002+ const calculationIndex = parseInt ( item . dataset . calculationIndex , 10 ) ;
2003+ if ( calculationIndex === deletedIndex ) {
2004+ item . remove ( ) ;
2005+ } else if ( calculationIndex > deletedIndex ) {
2006+ item . dataset . calculationIndex = calculationIndex - 1 ;
2007+ item . id = 'column-' + ( sourceColumnCount + calculationIndex - 1 ) ;
2008+ }
2009+ } ) ;
2010+ }
2011+
2012+ const calculations = OCA . Analytics . Filter . Drag . getCalculatedColumnItems ( ) ;
2013+ calculations . forEach ( ( calc , calculationIndex ) => {
2014+ const columnIndex = sourceColumnCount + calculationIndex ;
2015+ let item = document . querySelector ( '.draggable[data-calculation-index="' + calculationIndex + '"]' ) ;
2016+ if ( ! item ) {
2017+ item = OCA . Analytics . Filter . Drag . createDraggableItem (
2018+ calc . title || t ( 'analytics' , 'Calculated column' ) ,
2019+ columnIndex ,
2020+ calculationIndex
2021+ ) ;
2022+ rowsSection . appendChild ( item ) ;
2023+ } else {
2024+ item . id = 'column-' + columnIndex ;
2025+ const label = item . querySelector ( 'span' ) ;
2026+ if ( label ) {
2027+ label . textContent = calc . title || t ( 'analytics' , 'Calculated column' ) ;
2028+ }
2029+ }
2030+ } ) ;
2031+
2032+ document . querySelectorAll ( '.draggable[data-calculation-index]' ) . forEach ( item => {
2033+ if ( parseInt ( item . dataset . calculationIndex , 10 ) >= calculations . length ) {
2034+ item . remove ( ) ;
2035+ }
2036+ } ) ;
2037+ OCA . Analytics . Filter . Drag . managePlaceholders ( ) ;
2038+ } ,
2039+
19452040 /**
19462041 * Insert or remove drag-and-drop placeholders depending on section
19472042 * content to guide the user during layout editing.
0 commit comments