@@ -229,6 +229,141 @@ function applySearchText(data, searchText) {
229229 } ) ;
230230}
231231
232+ // Helper function to create group rows with aggregations
233+ function createGroupRow ( groupKey , groupValue , children , level , groupColumns ) {
234+ // Calculate aggregations for the group
235+ const totalValue = children . reduce (
236+ ( sum , child ) => sum + ( child . value || 0 ) ,
237+ 0 ,
238+ ) ;
239+ const totalAmountDelivered = children . reduce (
240+ ( sum , child ) => sum + ( child . amountDelivered || 0 ) ,
241+ 0 ,
242+ ) ;
243+ const avgPercentDelivered =
244+ children . length > 0
245+ ? children . reduce (
246+ ( sum , child ) => sum + ( child . percentDelivered || 0 ) ,
247+ 0 ,
248+ ) / children . length
249+ : 0 ;
250+ const totalEstimatedHours = children . reduce (
251+ ( sum , child ) => sum + ( child . estimatedHours || 0 ) ,
252+ 0 ,
253+ ) ;
254+ const totalActualHours = children . reduce (
255+ ( sum , child ) => sum + ( child . actualHours || 0 ) ,
256+ 0 ,
257+ ) ;
258+
259+ const groupRow = {
260+ // AG Grid group row properties
261+ group : true ,
262+ groupKey : groupKey || "" ,
263+
264+ // Aggregated values
265+ value : totalValue ,
266+ amountDelivered : totalAmountDelivered ,
267+ remaining : totalValue - totalAmountDelivered ,
268+ percentDelivered : Math . round ( avgPercentDelivered ) ,
269+ estimatedHours : totalEstimatedHours ,
270+ actualHours : totalActualHours ,
271+
272+ // Group metadata
273+ childCount : children . length ,
274+ expanded : false ,
275+ level : level ,
276+ } ;
277+
278+ // Add the group field dynamically if we have valid column info
279+ if ( groupColumns && groupColumns [ level ] ) {
280+ groupRow [ groupColumns [ level ] ] = groupValue ;
281+ }
282+
283+ return groupRow ;
284+ }
285+
286+ // Function to perform server-side grouping
287+ function performGrouping ( data , rowGroupCols , groupKeys ) {
288+ // Normalize rowGroupCols - it might be an array of objects or strings
289+ const groupColumns = Array . isArray ( rowGroupCols )
290+ ? rowGroupCols . map ( ( col ) =>
291+ typeof col === "string" ? col : col . field || col . id ,
292+ )
293+ : [ ] ;
294+
295+ if ( groupColumns . length === 0 ) {
296+ return data ; // No grouping requested
297+ }
298+
299+ // If we have group keys, we're fetching children of a specific group
300+ if ( groupKeys && groupKeys . length > 0 ) {
301+ // Filter data to match all group keys
302+ let filteredData = data ;
303+ for ( let i = 0 ; i < groupKeys . length ; i ++ ) {
304+ const groupCol = groupColumns [ i ] ;
305+ const groupValue = groupKeys [ i ] ;
306+ filteredData = filteredData . filter ( ( row ) => row [ groupCol ] === groupValue ) ;
307+ }
308+
309+ // If we've reached the deepest level, return leaf nodes
310+ if ( groupKeys . length === groupColumns . length ) {
311+ return filteredData ;
312+ }
313+
314+ // Otherwise, group by the next column
315+ const nextGroupCol = groupColumns [ groupKeys . length ] ;
316+ const groups = { } ;
317+
318+ filteredData . forEach ( ( row ) => {
319+ const groupValue = row [ nextGroupCol ] ;
320+ if ( ! groups [ groupValue ] ) {
321+ groups [ groupValue ] = [ ] ;
322+ }
323+ groups [ groupValue ] . push ( row ) ;
324+ } ) ;
325+
326+ // Create group rows
327+ const groupRows = [ ] ;
328+ Object . entries ( groups ) . forEach ( ( [ groupValue , children ] ) => {
329+ const groupKey = [ ...groupKeys , groupValue ] . join ( "|" ) ;
330+ groupRows . push (
331+ createGroupRow (
332+ groupKey ,
333+ groupValue ,
334+ children ,
335+ groupKeys . length ,
336+ groupColumns ,
337+ ) ,
338+ ) ;
339+ } ) ;
340+
341+ return groupRows ;
342+ }
343+
344+ // Top level grouping - group by first column
345+ const firstGroupCol = groupColumns [ 0 ] ;
346+ const groups = { } ;
347+
348+ data . forEach ( ( row ) => {
349+ const groupValue = row [ firstGroupCol ] ;
350+ if ( ! groups [ groupValue ] ) {
351+ groups [ groupValue ] = [ ] ;
352+ }
353+ groups [ groupValue ] . push ( row ) ;
354+ } ) ;
355+
356+ // Create top-level group rows
357+ const groupRows = [ ] ;
358+ Object . entries ( groups ) . forEach ( ( [ groupValue , children ] ) => {
359+ groupRows . push (
360+ createGroupRow ( groupValue , groupValue , children , 0 , groupColumns ) ,
361+ ) ;
362+ } ) ;
363+
364+ return groupRows ;
365+ }
366+
232367// Main function to process data request
233368export function processDataRequest ( {
234369 startRow = 0 ,
@@ -248,21 +383,21 @@ export function processDataRequest({
248383 // Apply filters
249384 data = applyFilters ( data , filterModel ) ;
250385
251- // Apply sorting
252- data = applySorting ( data , sortModel ) ;
253-
254386 // Handle row grouping if enabled
255387 if ( rowGroupCols && rowGroupCols . length > 0 ) {
256- // TODO: Implement proper grouping logic
257- // For now, just return flat data
258- console . log (
259- "Row grouping requested but not fully implemented:" ,
260- rowGroupCols ,
261- groupKeys ,
262- ) ;
388+ // Perform server-side grouping with aggregations
389+ data = performGrouping ( data , rowGroupCols , groupKeys ) ;
390+
391+ // Apply sorting to grouped data
392+ if ( sortModel && sortModel . length > 0 ) {
393+ data = applySorting ( data , sortModel ) ;
394+ }
395+ } else {
396+ // Apply sorting to flat data
397+ data = applySorting ( data , sortModel ) ;
263398 }
264399
265- // Get total after filtering
400+ // Get total after filtering and grouping
266401 const totalRows = data . length ;
267402
268403 // Apply pagination
0 commit comments