@@ -252,32 +252,274 @@ function triggerExcelDownload(data, fileName, sheetName) {
252252
253253// Move this above the DOMContentLoaded or make sure it's declared as a function
254254function openCenterSummaryModal ( summary ) {
255- const modal = document . getElementById ( 'centerSummaryModal' ) ;
256- const container = document . getElementById ( 'centerSummaryTableContainer' ) ;
257- container . innerHTML = '' ;
258- const table = document . createElement ( 'table' ) ;
259- table . innerHTML = `<thead><tr><th>Center</th><th>Total</th><th>Males</th><th>Females</th></tr></thead>` ;
260- const tbody = document . createElement ( 'tbody' ) ;
261- const sortedSummary = Object . entries ( summary ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) ) ;
255+ const modal = document . getElementById ( 'centerSummaryModal' ) ;
256+ const container = document . getElementById ( 'centerSummaryTableContainer' ) ;
257+ container . innerHTML = '' ;
258+
259+ const table = document . createElement ( 'table' ) ;
260+ table . innerHTML = `
261+ <thead>
262+ <tr>
263+ <th>Center</th>
264+ <th>Package</th>
265+ <th>Total</th>
266+ <th>Males</th>
267+ <th>Females</th>
268+ </tr>
269+ </thead>
270+ ` ;
271+
272+ const tbody = document . createElement ( 'tbody' ) ;
273+
274+ let grandTotal = 0 , grandM = 0 , grandF = 0 ;
275+ const packageTotals = { } ; // totals across all centers
276+ const packageCenterMap = { } ; // package → center breakup
277+
278+ const sortedCenters = Object . keys ( summary ) . sort ( ) ;
279+
280+ // ================= CENTER → PACKAGE =================
281+ sortedCenters . forEach ( center => {
282+ const centerData = summary [ center ] ;
283+ const centerRowId = `center-${ center . replace ( / \s + / g, '_' ) } ` ;
284+
285+ grandTotal += centerData . total ;
286+ grandM += centerData . M ;
287+ grandF += centerData . F ;
288+
289+ tbody . innerHTML += `
290+ <tr style="cursor:pointer;font-weight:bold;background:#f9f9f9;"
291+ onclick="toggleRows('${ centerRowId } ')">
292+ <td>${ center } </td>
293+ <td>All Packages</td>
294+ <td>${ centerData . total } </td>
295+ <td>${ centerData . M } </td>
296+ <td>${ centerData . F } </td>
297+ </tr>
298+ ` ;
299+
300+ Object . entries ( centerData . packages )
301+ . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) )
302+ . forEach ( ( [ pkg , pkgData ] ) => {
303+
304+ // collect package totals
305+ if ( ! packageTotals [ pkg ] ) {
306+ packageTotals [ pkg ] = { total :0 , M :0 , F :0 } ;
307+ packageCenterMap [ pkg ] = { } ;
308+ }
309+
310+ packageTotals [ pkg ] . total += pkgData . total ;
311+ packageTotals [ pkg ] . M += pkgData . M ;
312+ packageTotals [ pkg ] . F += pkgData . F ;
313+
314+ packageCenterMap [ pkg ] [ center ] = pkgData ;
315+
316+ tbody . innerHTML += `
317+ <tr data-parent="${ centerRowId } " style="display:none;">
318+ <td></td>
319+ <td>${ pkg } </td>
320+ <td>${ pkgData . total } </td>
321+ <td>${ pkgData . M } </td>
322+ <td>${ pkgData . F } </td>
323+ </tr>
324+ ` ;
325+ } ) ;
326+ } ) ;
262327
263- const rowsHtml = sortedSummary . map ( ( [ center , data ] ) => {
264- return `<tr><td>${ center } </td><td>${ data . total } </td><td>${ data . M } </td><td>${ data . F } </td></tr>` ;
265- } ) . join ( '' ) ;
328+ // ================= GRAND TOTAL =================
329+ tbody . innerHTML += `
330+ <tr style="font-weight:bold;background:#eaeaea;">
331+ <td>GRAND TOTAL</td>
332+ <td>All Packages</td>
333+ <td>${ grandTotal } </td>
334+ <td>${ grandM } </td>
335+ <td>${ grandF } </td>
336+ </tr>
337+ ` ;
338+
339+ // ================= PACKAGE → CENTER =================
340+ Object . entries ( packageTotals )
341+ . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) )
342+ . forEach ( ( [ pkg , data ] ) => {
343+
344+ const pkgRowId = `pkg-${ pkg . replace ( / \s + / g, '_' ) } ` ;
345+
346+ // package total row (CLICKABLE)
347+ tbody . innerHTML += `
348+ <tr style="cursor:pointer;font-weight:bold;background:#f4f6ff;"
349+ onclick="toggleRows('${ pkgRowId } ')">
350+ <td>All Centers</td>
351+ <td>${ pkg } </td>
352+ <td>${ data . total } </td>
353+ <td>${ data . M } </td>
354+ <td>${ data . F } </td>
355+ </tr>
356+ ` ;
357+
358+ // center rows under package
359+ Object . entries ( packageCenterMap [ pkg ] )
360+ . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) )
361+ . forEach ( ( [ center , cData ] ) => {
362+ tbody . innerHTML += `
363+ <tr data-parent="${ pkgRowId } " style="display:none;">
364+ <td>${ center } </td>
365+ <td></td>
366+ <td>${ cData . total } </td>
367+ <td>${ cData . M } </td>
368+ <td>${ cData . F } </td>
369+ </tr>
370+ ` ;
371+ } ) ;
372+
373+ } ) ;
374+
375+ table . appendChild ( tbody ) ;
376+ container . appendChild ( table ) ;
377+ // Add Download Excel button (once)
378+ if ( ! document . getElementById ( 'downloadCenterSummaryExcel' ) ) {
379+ const btn = document . createElement ( 'button' ) ;
380+ btn . id = 'downloadCenterSummaryExcel' ;
381+ btn . className = 'btn btn-primary' ;
382+ btn . style . marginBottom = '10px' ;
383+ btn . innerText = 'Download Summary Excel' ;
384+
385+ btn . onclick = ( ) => downloadCenterSummaryExcel ( summary ) ;
386+
387+ container . prepend ( btn ) ;
388+ }
389+
390+ modal . style . display = 'block' ;
266391
267- tbody . innerHTML = rowsHtml ;
268- table . appendChild ( tbody ) ; container . appendChild ( table ) ; modal . style . display = 'block' ;
269- document . querySelector ( '#centerSummaryModal .close' ) . onclick = ( ) => modal . style . display = 'none' ;
270- window . onclick = e => { if ( e . target === modal ) modal . style . display = 'none' ; } ;
392+
393+ document . querySelector ( '#centerSummaryModal .close' ) . onclick = ( ) =>
394+ modal . style . display = 'none' ;
395+
396+ window . onclick = e => {
397+ if ( e . target === modal ) modal . style . display = 'none' ;
398+ } ;
271399}
272400
273401function getCenterWiseSummary ( bookings ) {
274402 const map = { } ;
403+
275404 bookings . forEach ( b => {
276405 const center = b . center || 'Unknown' ;
277- if ( ! map [ center ] ) map [ center ] = { total : 0 , M : 0 , F : 0 } ;
278- map [ center ] . total += 1 ;
279- if ( b . gender === 'M' ) map [ center ] . M += 1 ;
280- if ( b . gender === 'F' ) map [ center ] . F += 1 ;
406+ const pkg = b . package_name || 'Unknown Package' ;
407+ const gender = b . gender ;
408+
409+ if ( ! map [ center ] ) {
410+ map [ center ] = {
411+ total : 0 ,
412+ M : 0 ,
413+ F : 0 ,
414+ packages : { }
415+ } ;
416+ }
417+
418+ map [ center ] . total ++ ;
419+ if ( gender === 'M' ) map [ center ] . M ++ ;
420+ if ( gender === 'F' ) map [ center ] . F ++ ;
421+
422+ if ( ! map [ center ] . packages [ pkg ] ) {
423+ map [ center ] . packages [ pkg ] = { total : 0 , M : 0 , F : 0 } ;
424+ }
425+
426+ map [ center ] . packages [ pkg ] . total ++ ;
427+ if ( gender === 'M' ) map [ center ] . packages [ pkg ] . M ++ ;
428+ if ( gender === 'F' ) map [ center ] . packages [ pkg ] . F ++ ;
281429 } ) ;
430+
282431 return map ;
283432}
433+
434+
435+ function togglePackageRows ( className ) {
436+ document . querySelectorAll ( `.${ className } ` ) . forEach ( row => {
437+ row . style . display = row . style . display === 'none' ? 'table-row' : 'none' ;
438+ } ) ;
439+ }
440+
441+ function toggleRows ( groupKey ) {
442+ const rows = document . querySelectorAll ( `tr[data-parent="${ groupKey } "]` ) ;
443+ const shouldShow = [ ...rows ] . some ( r => r . style . display === 'none' ) ;
444+
445+ rows . forEach ( row => {
446+ row . style . display = shouldShow ? 'table-row' : 'none' ;
447+ } ) ;
448+ }
449+
450+
451+ function downloadCenterSummaryExcel ( summary ) {
452+ const wb = XLSX . utils . book_new ( ) ;
453+
454+ /* =======================
455+ SHEET 1: CENTER SUMMARY
456+ ======================= */
457+ const centerRows = [ ] ;
458+ centerRows . push ( [ 'Center' , 'Package' , 'Total' , 'Males' , 'Females' ] ) ;
459+
460+ let grandTotal = 0 , grandM = 0 , grandF = 0 ;
461+ const packageTotals = { } ;
462+ const packageCenterMap = { } ;
463+
464+ Object . keys ( summary ) . sort ( ) . forEach ( center => {
465+ const c = summary [ center ] ;
466+
467+ centerRows . push ( [ center , 'All Packages' , c . total , c . M , c . F ] ) ;
468+
469+ grandTotal += c . total ;
470+ grandM += c . M ;
471+ grandF += c . F ;
472+
473+ Object . entries ( c . packages ) . forEach ( ( [ pkg , p ] ) => {
474+ centerRows . push ( [ '' , pkg , p . total , p . M , p . F ] ) ;
475+
476+ if ( ! packageTotals [ pkg ] ) {
477+ packageTotals [ pkg ] = { total :0 , M :0 , F :0 } ;
478+ packageCenterMap [ pkg ] = { } ;
479+ }
480+
481+ packageTotals [ pkg ] . total += p . total ;
482+ packageTotals [ pkg ] . M += p . M ;
483+ packageTotals [ pkg ] . F += p . F ;
484+
485+ packageCenterMap [ pkg ] [ center ] = p ;
486+ } ) ;
487+ } ) ;
488+
489+ centerRows . push ( [ ] ) ;
490+ centerRows . push ( [ 'GRAND TOTAL' , 'All Packages' , grandTotal , grandM , grandF ] ) ;
491+
492+ Object . entries ( packageTotals ) . forEach ( ( [ pkg , d ] ) => {
493+ centerRows . push ( [ 'All Centers' , pkg , d . total , d . M , d . F ] ) ;
494+ } ) ;
495+
496+ const ws1 = XLSX . utils . aoa_to_sheet ( centerRows ) ;
497+ XLSX . utils . book_append_sheet ( wb , ws1 , 'Center Summary' ) ;
498+
499+ /* =========================
500+ SHEET 2: PACKAGE BREAKDOWN
501+ ========================= */
502+ const pkgRows = [ ] ;
503+
504+ Object . keys ( packageCenterMap ) . sort ( ) . forEach ( pkg => {
505+ pkgRows . push ( [ pkg ] ) ; // Package title row
506+ pkgRows . push ( [ 'Center' , 'Total' , 'Males' , 'Females' ] ) ;
507+
508+ Object . keys ( packageCenterMap [ pkg ] )
509+ . sort ( )
510+ . forEach ( center => {
511+ const d = packageCenterMap [ pkg ] [ center ] ;
512+ pkgRows . push ( [ center , d . total , d . M , d . F ] ) ;
513+ } ) ;
514+
515+ pkgRows . push ( [ ] ) ; // spacing between packages
516+ } ) ;
517+
518+ const ws2 = XLSX . utils . aoa_to_sheet ( pkgRows ) ;
519+ XLSX . utils . book_append_sheet ( wb , ws2 , 'Package Breakdown' ) ;
520+
521+ /* ===================
522+ DOWNLOAD FILE
523+ =================== */
524+ XLSX . writeFile ( wb , 'center_package_summary.xlsx' ) ;
525+ }
0 commit comments