Subversion Repositories SmartDukaan

Rev

Rev 36024 | Rev 36368 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 36024 Rev 36242
Line 316... Line 316...
316
	}
316
	}
317
 
317
 
318
	.store-header {
318
	.store-header {
319
		border-bottom: 2px solid #27ae60;
319
		border-bottom: 2px solid #27ae60;
320
	}
320
	}
-
 
321
 
-
 
322
	/* Per-row "Delay" summary column on the matrix view.
-
 
323
	   Surfaces the worst-overdue event for each partner so users can spot the
-
 
324
	   biggest blockers without scanning across all 19 stage cells. */
-
 
325
	.tl-delay-cell {
-
 
326
		text-align: center;
-
 
327
		min-width: 110px;
-
 
328
		line-height: 1.3;
-
 
329
	}
-
 
330
 
-
 
331
	.tl-delay-chip {
-
 
332
		display: inline-block;
-
 
333
		padding: 2px 7px;
-
 
334
		border-radius: 10px;
-
 
335
		font-size: 10px;
-
 
336
		font-weight: 700;
-
 
337
		color: #fff;
-
 
338
	}
-
 
339
 
-
 
340
	.tl-delay-chip.high {
-
 
341
		background: #d9534f;
-
 
342
	}
-
 
343
 
-
 
344
	.tl-delay-chip.med {
-
 
345
		background: #f0ad4e;
-
 
346
	}
-
 
347
 
-
 
348
	.tl-delay-chip.low {
-
 
349
		background: #f7dc6f;
-
 
350
		color: #333;
-
 
351
	}
-
 
352
 
-
 
353
	.tl-delay-team {
-
 
354
		display: block;
-
 
355
		font-size: 10px;
-
 
356
		color: #555;
-
 
357
		margin-top: 2px;
-
 
358
		max-width: 110px;
-
 
359
		overflow: hidden;
-
 
360
		text-overflow: ellipsis;
-
 
361
		white-space: nowrap;
-
 
362
	}
-
 
363
 
-
 
364
	.tl-delay-none {
-
 
365
		color: #ccc;
-
 
366
	}
-
 
367
 
-
 
368
	/* "Show delayed only" filter toggle styling — sits next to the legend in
-
 
369
	   the DataTables top-controls flex row. */
-
 
370
	.tl-delay-filter {
-
 
371
		display: inline-flex;
-
 
372
		align-items: center;
-
 
373
		gap: 6px;
-
 
374
		font-size: 11px;
-
 
375
		font-weight: 600;
-
 
376
		color: #555;
-
 
377
		margin-left: 16px;
-
 
378
		cursor: pointer;
-
 
379
		user-select: none;
-
 
380
	}
-
 
381
 
-
 
382
	.tl-delay-filter input[type="checkbox"] {
-
 
383
		margin: 0;
-
 
384
		cursor: pointer;
-
 
385
	}
-
 
386
 
-
 
387
	.tl-delay-filter.active {
-
 
388
		color: #d9534f;
-
 
389
	}
-
 
390
 
-
 
391
	/* Tab strip — sits between the breadcrumb and the matrix table.
-
 
392
	   Three preset views over the same data: All / Delayed Only / By Team. */
-
 
393
	.tl-tab-strip {
-
 
394
		display: flex;
-
 
395
		align-items: center;
-
 
396
		gap: 4px;
-
 
397
		padding: 8px 0 12px;
-
 
398
		border-bottom: 1px solid #e0e0e0;
-
 
399
		margin-bottom: 10px;
-
 
400
	}
-
 
401
 
-
 
402
	.tl-tab {
-
 
403
		background: #fff;
-
 
404
		border: 1px solid #d0d7de;
-
 
405
		border-bottom: none;
-
 
406
		border-radius: 4px 4px 0 0;
-
 
407
		padding: 8px 16px;
-
 
408
		font-size: 12px;
-
 
409
		font-weight: 600;
-
 
410
		color: #555;
-
 
411
		cursor: pointer;
-
 
412
		position: relative;
-
 
413
		bottom: -1px;
-
 
414
		transition: background 0.15s, color 0.15s;
-
 
415
	}
-
 
416
 
-
 
417
	.tl-tab:hover {
-
 
418
		background: #f5f7fa;
-
 
419
		color: #2c3e50;
-
 
420
	}
-
 
421
 
-
 
422
	.tl-tab.active {
-
 
423
		background: #2c3e50;
-
 
424
		color: #fff;
-
 
425
		border-color: #2c3e50;
-
 
426
	}
-
 
427
 
-
 
428
	.tl-tab i {
-
 
429
		margin-right: 5px;
-
 
430
	}
-
 
431
 
-
 
432
	.tl-tab-count {
-
 
433
		display: inline-block;
-
 
434
		margin-left: 6px;
-
 
435
		padding: 1px 7px;
-
 
436
		background: #d9534f;
-
 
437
		color: #fff;
-
 
438
		border-radius: 10px;
-
 
439
		font-size: 10px;
-
 
440
		font-weight: 700;
-
 
441
	}
-
 
442
 
-
 
443
	.tl-tab.active .tl-tab-count {
-
 
444
		background: #fff;
-
 
445
		color: #d9534f;
-
 
446
	}
-
 
447
 
-
 
448
	.tl-team-picker {
-
 
449
		margin-left: auto;
-
 
450
		display: flex;
-
 
451
		align-items: center;
-
 
452
		gap: 8px;
-
 
453
	}
-
 
454
 
-
 
455
	.tl-team-picker select {
-
 
456
		height: 30px;
-
 
457
		font-size: 12px;
-
 
458
		min-width: 200px;
-
 
459
	}
321
</style>
460
</style>
322
 
461
 
323
<section class="wrapper">
462
<section class="wrapper">
324
	<div class="row">
463
	<div class="row">
325
		<div class="col-lg-12">
464
		<div class="col-lg-12">
Line 331... Line 470...
331
				<li><i class="icon_document_alt"></i>Onboarding Timeline</li>
470
				<li><i class="icon_document_alt"></i>Onboarding Timeline</li>
332
			</ol>
471
			</ol>
333
		</div>
472
		</div>
334
	</div>
473
	</div>
335
 
474
 
-
 
475
	## Tab strip — three preset views over the same matrix.
-
 
476
	## "all" = no filter, "delayed" = only rows with active delays, "team" = delayed + team filter dropdown.
-
 
477
	## Each tab is a button that flips JS filter state and re-draws the DataTable.
-
 
478
	<div class="col-lg-12 tl-tab-strip">
-
 
479
		<button type="button" class="tl-tab active" data-tab="all">
-
 
480
			<i class="fa fa-list"></i> All Partners
-
 
481
		</button>
-
 
482
		<button type="button" class="tl-tab" data-tab="delayed">
-
 
483
			<i class="fa fa-exclamation-triangle"></i> Delayed Only
-
 
484
			#if($worstDelayMap && $worstDelayMap.size() > 0)
-
 
485
				<span class="tl-tab-count">$worstDelayMap.size()</span>
-
 
486
			#end
-
 
487
		</button>
-
 
488
		<button type="button" class="tl-tab" data-tab="team">
-
 
489
			<i class="fa fa-users"></i> By Team
-
 
490
		</button>
-
 
491
		<div class="tl-team-picker" id="tlTeamPicker" style="display:none;">
-
 
492
			<select id="tlTeamFilter" class="form-control input-sm">
-
 
493
				<option value="">All teams</option>
-
 
494
			</select>
-
 
495
		</div>
-
 
496
		<button type="button" class="btn btn-sm btn-info" id="btnShowSummary" style="margin-left:auto;">
-
 
497
			<i class="fa fa-bar-chart"></i> Show Summary
-
 
498
		</button>
-
 
499
	</div>
-
 
500
 
-
 
501
	<div class="col-lg-12" style="padding:8px 0 4px;">
-
 
502
		<div class="legend" style="justify-content:flex-start;">
-
 
503
			<div class="legend-item"><span class="tl-dot completed"></span> Done</div>
-
 
504
			<div class="legend-item"><span class="tl-dot completed" style="outline:3px solid #ffe0e0;"></span><span
-
 
505
					style="background:#fff0f0;padding:1px 5px;border-radius:3px;margin-left:2px;">Done (crossed TAT)</span>
-
 
506
			</div>
-
 
507
			<div class="legend-item"><span class="tl-dot delay"></span> Crossed TAT</div>
-
 
508
			<div class="legend-item"><span class="tl-dot wip"></span> On Time</div>
-
 
509
			<div class="legend-item"><span class="tl-dot not-started"></span> Not Started</div>
-
 
510
		</div>
-
 
511
	</div>
-
 
512
 
336
	<div class="col-lg-12" style="overflow-x:auto;">
513
	<div class="col-lg-12" style="overflow-x:auto;">
337
		<table class="timeline-table" id="storeTimeline">
514
		<table class="timeline-table" id="storeTimeline">
338
			<thead>
515
			<thead>
339
			<tr>
516
			<tr>
340
				<th rowspan="2">ID</th>
517
				<th rowspan="2">ID</th>
341
				<th rowspan="2">Partner</th>
518
				<th rowspan="2">Partner</th>
342
				<th rowspan="2">Code</th>
519
				<th rowspan="2">Code</th>
343
				<th rowspan="2">Status</th>
520
				<th rowspan="2">Status</th>
-
 
521
				<th rowspan="2" style="background:#c0392b;">Delay</th>
344
				<th colspan="4" style="background:#8e44ad;"><span class="section-header">LOI Process</span></th>
522
				<th colspan="4" style="background:#8e44ad;"><span class="section-header">LOI Process</span></th>
345
				<th colspan="4" style="background:#2471a3;"><span class="section-header">Onboarding</span></th>
523
				<th colspan="4" style="background:#2471a3;"><span class="section-header">Onboarding</span></th>
346
				<th colspan="8" style="background:#1e8449;"><span class="section-header">Store Setup</span></th>
524
				<th colspan="8" style="background:#1e8449;"><span class="section-header">Store Setup</span></th>
347
				<th colspan="3" style="background:#d35400;"><span class="section-header">Launch</span></th>
525
				<th colspan="3" style="background:#d35400;"><span class="section-header">Launch</span></th>
348
			</tr>
526
			</tr>
Line 372... Line 550...
372
				<th style="background:#e67e22;">Training</th>
550
				<th style="background:#e67e22;">Training</th>
373
			</tr>
551
			</tr>
374
			</thead>
552
			</thead>
375
			<tbody>
553
			<tbody>
376
				#foreach($st in $storeTimelines)
554
				#foreach($st in $storeTimelines)
-
 
555
					## Lookup per-row delay metadata once. partnerDelayMap maps eventName -> DelayReportItem
-
 
556
					## (used for hover tooltips on DELAY cells). worstDelay is the single worst-overdue
-
 
557
					## item for this partner (drives the new "Delay" column and the row-level filter).
-
 
558
					#set($partnerDelayMap = $delayIndex.get($st.getOnboardingId()))
-
 
559
					#set($worstDelay = $worstDelayMap.get($st.getOnboardingId()))
-
 
560
					#if($worstDelay)
-
 
561
					<tr data-has-delay="true" data-delay-team="$!{worstDelay.getResponsibleTeam()}">
-
 
562
					#else
-
 
563
					<tr data-has-delay="false" data-delay-team="">
377
				<tr>
564
					#end
378
					<td><strong>$st.getOnboardingId()</strong></td>
565
					<td><strong>$st.getOnboardingId()</strong></td>
379
					<td style="text-align:left;max-width:120px;overflow:hidden;text-overflow:ellipsis;"
566
					<td style="text-align:left;max-width:120px;overflow:hidden;text-overflow:ellipsis;"
380
						title="$st.getOutletName() ($st.getCity())">$st.getOutletName() ($st.getCity())
567
						title="$st.getOutletName() ($st.getCity())">$st.getOutletName() ($st.getCity())
381
					</td>
568
					</td>
382
					#if($st.getCode())
569
					#if($st.getCode())
Line 396... Line 583...
396
							<span class="status-badge status-hold">$st.getStatus()</span>
583
							<span class="status-badge status-hold">$st.getStatus()</span>
397
						#else
584
						#else
398
							<span class="status-badge status-rejected">$st.getStatus()</span>
585
							<span class="status-badge status-rejected">$st.getStatus()</span>
399
						#end
586
						#end
400
					</td>
587
					</td>
-
 
588
					## --- New "Delay" summary column ---
-
 
589
					## Renders the worst-overdue event with a severity-coloured chip + responsible team.
-
 
590
					## Sort key (data-order) is daysOverdue so DataTables sorts numerically, not lexically.
-
 
591
					#if($worstDelay)
-
 
592
						#if($worstDelay.getDaysOverdue() > 7)
-
 
593
							#set($delaySeverity = "high")
-
 
594
						#elseif($worstDelay.getDaysOverdue() > 3)
-
 
595
							#set($delaySeverity = "med")
-
 
596
						#else
-
 
597
							#set($delaySeverity = "low")
-
 
598
						#end
-
 
599
						<td class="tl-delay-cell" data-order="$worstDelay.getDaysOverdue()"
-
 
600
							title="$worstDelay.getEventName() - planned $worstDelay.getPlannedDate().format($dateFormatter), overdue $worstDelay.getDaysOverdue() day(s), team: $worstDelay.getResponsibleTeam()">
-
 
601
							<span class="tl-delay-chip $delaySeverity">$worstDelay.getDaysOverdue()d overdue</span>
-
 
602
							<span class="tl-delay-team">$worstDelay.getResponsibleTeam()</span>
-
 
603
						</td>
-
 
604
					#else
-
 
605
						<td class="tl-delay-cell tl-delay-none" data-order="-1">-</td>
-
 
606
					#end
-
 
607
					## Lookup late-completion metadata for this partner once.
-
 
608
					## latePartnerMap maps eventName -> DelayReportItem for events completed
-
 
609
					## after their TAT deadline (daysOverdue = how many days late).
-
 
610
					#set($latePartnerMap = false)
-
 
611
					#if($lateIndex)
-
 
612
						#set($latePartnerMap = $lateIndex.get($st.getOnboardingId()))
-
 
613
					#end
401
					#if($st.getObtm() && $st.getObtm().size() > 0)
614
					#if($st.getObtm() && $st.getObtm().size() > 0)
402
						#foreach($obtm in $st.getObtm())
615
						#foreach($obtm in $st.getObtm())
403
							#if($obtm.getStatus().toString() == "COMPLETED")
616
							#if($obtm.getStatus().toString() == "COMPLETED")
-
 
617
								## Check if this event was completed late (after TAT deadline)
-
 
618
								#set($cellLate = false)
-
 
619
								#if($latePartnerMap)
-
 
620
									#set($cellLate = $latePartnerMap.get($obtm.getTitle().name()))
-
 
621
								#end
-
 
622
								#if($cellLate)
-
 
623
									<td style="background:#fff0f0;"
-
 
624
										title="Completed $cellLate.getDaysOverdue() day(s) late (planned: $cellLate.getPlannedDate().format($dateFormatter), actual: $obtm.getCompletedTimestamp().format($dateFormatter))"><span
-
 
625
											class="tl-cell"><span
-
 
626
											class="tl-dot completed"></span>#if($obtm.getCompletedTimestamp())<span
-
 
627
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
-
 
628
									</td>
-
 
629
								#else
404
								<td><span class="tl-cell"><span
630
									<td><span class="tl-cell"><span
405
										class="tl-dot completed"></span>#if($obtm.getCompletedTimestamp())<span
631
											class="tl-dot completed"></span>#if($obtm.getCompletedTimestamp())<span
406
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
632
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
407
								</td>
633
									</td>
-
 
634
								#end
408
							#elseif($obtm.getStatus().toString() == "DELAY")
635
							#elseif($obtm.getStatus().toString() == "DELAY")
-
 
636
								## Look up the matching DelayReportItem to enrich this red cell with
-
 
637
								## planned date, days overdue, and responsible team in the title tooltip.
-
 
638
								## $obtm.getTitle() is a StoreTimeline enum; .name() gives the string key.
-
 
639
								#set($cellDelay = false)
-
 
640
								#if($partnerDelayMap)
-
 
641
									#set($cellDelay = $partnerDelayMap.get($obtm.getTitle().name()))
-
 
642
								#end
-
 
643
								#if($cellDelay)
409
								<td style="background:#fff0f0;"><span class="tl-cell"><span
644
									<td style="background:#fff0f0;"
-
 
645
										title="Planned: $cellDelay.getPlannedDate().format($dateFormatter) | Overdue: $cellDelay.getDaysOverdue() day(s) | Team: $cellDelay.getResponsibleTeam()"><span
-
 
646
											class="tl-cell"><span
-
 
647
											class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
-
 
648
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
-
 
649
									</td>
-
 
650
								#else
-
 
651
									<td style="background:#fff0f0;"
-
 
652
										title="Delayed (planned $!{obtm.getCompletedTimestamp().format($dateFormatter)})"><span
-
 
653
											class="tl-cell"><span
410
										class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
654
											class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
411
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
655
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
412
								</td>
656
									</td>
-
 
657
								#end
413
							#elseif($obtm.getStatus().toString() == "WIP")
658
							#elseif($obtm.getStatus().toString() == "WIP")
414
								<td style="background:#fffde7;"><span class="tl-cell"><span
659
								<td style="background:#fffde7;"><span class="tl-cell"><span
415
										class="tl-dot wip"></span>#if($obtm.getCompletedTimestamp())<span
660
										class="tl-dot wip"></span>#if($obtm.getCompletedTimestamp())<span
416
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
661
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
417
								</td>
662
								</td>
Line 451... Line 696...
451
	</div>
696
	</div>
452
</section>
697
</section>
453
 
698
 
454
<script type="text/javascript">
699
<script type="text/javascript">
455
	$(document).ready(function () {
700
	$(document).ready(function () {
-
 
701
		// Custom DataTables search filter — applies the active tab's filter rules.
-
 
702
		// Tab state lives on window so it survives the AJAX re-render of this panel:
-
 
703
		//   storeTimelineActiveTab: 'all' | 'delayed' | 'team'
-
 
704
		//   storeTimelineTeamFilter: string (only meaningful when tab === 'team')
-
 
705
		// Filter is registered once on window to stay idempotent across re-loads.
-
 
706
		window.storeTimelineActiveTab = window.storeTimelineActiveTab || 'all';
-
 
707
		window.storeTimelineTeamFilter = window.storeTimelineTeamFilter || '';
-
 
708
		if (!window.storeTimelineDelayFilterRegistered) {
-
 
709
			window.storeTimelineDelayFilterRegistered = true;
-
 
710
			$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
-
 
711
				if (settings.nTable.id !== 'storeTimeline') return true;
-
 
712
				var tab = window.storeTimelineActiveTab;
-
 
713
				if (tab === 'all') return true;
-
 
714
				var rowNode = settings.aoData[dataIndex].nTr;
-
 
715
				// Both 'delayed' and 'team' tabs require an active delay
-
 
716
				if ($(rowNode).attr('data-has-delay') !== 'true') return false;
-
 
717
				if (tab === 'team' && window.storeTimelineTeamFilter) {
-
 
718
					return $(rowNode).attr('data-delay-team') === window.storeTimelineTeamFilter;
-
 
719
				}
-
 
720
				return true;
-
 
721
			});
-
 
722
		}
-
 
723
 
456
		var dtable = $('#storeTimeline').DataTable({
724
		var dtable = $('#storeTimeline').DataTable({
457
			scrollX: true,
725
			scrollX: true,
458
			scrollY: '75vh',
726
			scrollY: '75vh',
459
			scrollCollapse: true,
727
			scrollCollapse: true,
460
			orderCellsTop: true,
728
			orderCellsTop: true,
461
			order: [[0, "desc"]],
729
			order: [[0, "desc"]],
462
			paging: false,
730
			paging: false,
463
			fixedColumns: {
731
			fixedColumns: {
464
				leftColumns: 4
732
				leftColumns: 5
465
			},
733
			},
466
			dom: '<"top-controls"f>rt<"bottom"i>',
734
			dom: '<"top-controls"f>rt<"bottom"i>',
467
			language: {
735
			language: {
468
				search: "Search Partners:"
736
				search: "Search Partners:"
469
			}
737
			}
470
		});
738
		});
471
 
739
 
-
 
740
		// Legend is now rendered as static HTML above the table (not inside DataTables controls).
-
 
741
 
472
		// Insert legend between length and filter controls
742
		// Populate the team dropdown from the unique data-delay-team values on rows.
-
 
743
		// Only delayed rows have a team set, so this naturally excludes empty values.
-
 
744
		var teamSet = {};
-
 
745
		$('#storeTimeline tbody tr[data-has-delay="true"]').each(function () {
473
		var legendHtml = '<div class="timeline-legend-row">' +
746
			var team = $(this).attr('data-delay-team');
474
				'<div class="legend">' +
747
			if (team) teamSet[team] = true;
-
 
748
		});
-
 
749
		var teamSelect = $('#tlTeamFilter');
-
 
750
		Object.keys(teamSet).sort().forEach(function (team) {
475
				'<div class="legend-item"><span class="tl-dot completed"></span> Completed</div>' +
751
			teamSelect.append($('<option></option>').val(team).text(team));
-
 
752
		});
-
 
753
 
-
 
754
		// applyTab — single source of truth for switching tabs.
476
				'<div class="legend-item"><span class="tl-dot wip"></span> In Progress</div>' +
755
		// Updates window state, button highlighting, dropdown visibility,
-
 
756
		// sort order, and triggers a DataTable redraw.
-
 
757
		function applyTab(tab) {
-
 
758
			window.storeTimelineActiveTab = tab;
-
 
759
			$('.tl-tab').removeClass('active');
477
				'<div class="legend-item"><span class="tl-dot delay"></span> Delayed</div>' +
760
			$('.tl-tab[data-tab="' + tab + '"]').addClass('active');
-
 
761
			$('#tlTeamPicker').toggle(tab === 'team');
-
 
762
			if (tab !== 'team') {
-
 
763
				window.storeTimelineTeamFilter = '';
-
 
764
				teamSelect.val('');
-
 
765
			}
-
 
766
			dtable.order([0, 'desc']).draw();
-
 
767
		}
-
 
768
 
-
 
769
		// Tab click → apply the corresponding filter view
-
 
770
		$('.tl-tab').on('click', function () {
-
 
771
			applyTab($(this).data('tab'));
-
 
772
		});
-
 
773
 
478
				'<div class="legend-item"><span class="tl-dot not-started"></span> Not Started</div>' +
774
		// Team dropdown change → re-apply the team filter (only meaningful on the By Team tab)
-
 
775
		teamSelect.on('change', function () {
-
 
776
			window.storeTimelineTeamFilter = $(this).val();
479
				'</div></div>';
777
			dtable.draw();
-
 
778
		});
-
 
779
 
-
 
780
		// Honor an optional defaultTab model attribute set by the controller.
480
		$('#storeTimeline_wrapper .dataTables_length').after(legendHtml);
781
		// Used by /partnerDelayPanel (which now delegates here) to land users
-
 
782
		// on the Delayed Only tab without an extra click.
-
 
783
		var initialTab = '$!{defaultTab}';
-
 
784
		if (initialTab === 'delayed' || initialTab === 'team') {
-
 
785
			applyTab(initialTab);
-
 
786
		}
481
 
787
 
482
		// Row click -> load detail panel
788
		// Row click -> load detail panel
483
		$('#storeTimeline tbody').on('click', 'tr', function () {
789
		$('#storeTimeline tbody').on('click', 'tr', function () {
484
			var $row = $(this);
790
			var $row = $(this);
485
			var onboardingId = $.trim($row.find('td:first').text());
791
			var onboardingId = $.trim($row.find('td:first').text());
Line 519... Line 825...
519
			$('#timeline-detail-container').hide();
825
			$('#timeline-detail-container').hide();
520
			$('#storeTimeline_wrapper').show();
826
			$('#storeTimeline_wrapper').show();
521
			dtable.columns.adjust();
827
			dtable.columns.adjust();
522
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
828
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
523
		});
829
		});
-
 
830
 
-
 
831
		// Show Summary modal — renders Chart.js charts on first open
-
 
832
		var chartsRendered = false;
-
 
833
		$('#btnShowSummary').on('click', function () {
-
 
834
			$('#summaryModal').modal('show');
-
 
835
			if (chartsRendered) return;
-
 
836
			chartsRendered = true;
-
 
837
 
-
 
838
			// --- Chart 1: Delays by Stage (horizontal bar) ---
-
 
839
			var stageLabels = [#foreach($e in $delaysByStage.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
-
 
840
			var stageCounts = [#foreach($e in $delaysByStage.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
-
 
841
			new Chart(document.getElementById('chartDelaysByStage'), {
-
 
842
				type: 'bar',
-
 
843
				data: {
-
 
844
					labels: stageLabels,
-
 
845
					datasets: [{
-
 
846
						label: 'Delayed Partners',
-
 
847
						data: stageCounts,
-
 
848
						backgroundColor: '#e74c3c'
-
 
849
					}]
-
 
850
				},
-
 
851
				options: {
-
 
852
					indexAxis: 'y',
-
 
853
					responsive: true,
-
 
854
					maintainAspectRatio: false,
-
 
855
					plugins: {
-
 
856
						legend: {display: false},
-
 
857
						title: {display: true, text: 'Active Delays by Stage ($totalDelays total)', font: {size: 14}}
-
 
858
					},
-
 
859
					scales: {x: {beginAtZero: true, ticks: {stepSize: 1}}}
-
 
860
				}
-
 
861
			});
-
 
862
 
-
 
863
			// --- Chart 2: Delays by Team (donut) ---
-
 
864
			var teamLabels = [#foreach($e in $delaysByTeam.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
-
 
865
			var teamCounts = [#foreach($e in $delaysByTeam.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
-
 
866
			var teamColors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#e67e22', '#34495e'];
-
 
867
			new Chart(document.getElementById('chartDelaysByTeam'), {
-
 
868
				type: 'doughnut',
-
 
869
				data: {
-
 
870
					labels: teamLabels,
-
 
871
					datasets: [{data: teamCounts, backgroundColor: teamColors.slice(0, teamLabels.length)}]
-
 
872
				},
-
 
873
				options: {
-
 
874
					responsive: true,
-
 
875
					maintainAspectRatio: false,
-
 
876
					plugins: {
-
 
877
						title: {display: true, text: 'Delays by Responsible Team', font: {size: 14}},
-
 
878
						legend: {position: 'right'}
-
 
879
					}
-
 
880
				}
-
 
881
			});
-
 
882
 
-
 
883
			// --- Chart 3: Delay Severity (donut) ---
-
 
884
			new Chart(document.getElementById('chartDelaySeverity'), {
-
 
885
				type: 'doughnut',
-
 
886
				data: {
-
 
887
					labels: ['Critical (>7 days)', 'Medium (3-7 days)', 'Low (\u22643 days)'],
-
 
888
					datasets: [{
-
 
889
						data: [$severityCritical, $severityMedium, $severityLow],
-
 
890
						backgroundColor: ['#d9534f', '#f0ad4e', '#f7dc6f']
-
 
891
					}]
-
 
892
				},
-
 
893
				options: {
-
 
894
					responsive: true,
-
 
895
					maintainAspectRatio: false,
-
 
896
					plugins: {
-
 
897
						title: {display: true, text: 'Delay Severity Breakdown', font: {size: 14}},
-
 
898
						legend: {position: 'right'}
-
 
899
					}
-
 
900
				}
-
 
901
			});
-
 
902
 
-
 
903
			// --- Chart 4: Late Completions by Stage (horizontal bar) ---
-
 
904
			var lateLabels = [#foreach($e in $lateByStage.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
-
 
905
			var lateCounts = [#foreach($e in $lateByStage.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
-
 
906
			new Chart(document.getElementById('chartLateByStage'), {
-
 
907
				type: 'bar',
-
 
908
				data: {
-
 
909
					labels: lateLabels,
-
 
910
					datasets: [{
-
 
911
						label: 'Late Completions',
-
 
912
						data: lateCounts,
-
 
913
						backgroundColor: '#e67e22'
-
 
914
					}]
-
 
915
				},
-
 
916
				options: {
-
 
917
					indexAxis: 'y',
-
 
918
					responsive: true,
-
 
919
					maintainAspectRatio: false,
-
 
920
					plugins: {
-
 
921
						legend: {display: false},
-
 
922
						title: {display: true, text: 'Late Completions by Stage ($totalLate total)', font: {size: 14}}
-
 
923
					},
-
 
924
					scales: {x: {beginAtZero: true, ticks: {stepSize: 1}}}
-
 
925
				}
-
 
926
			});
-
 
927
		});
524
	});
928
	});
525
</script>
-
 
526
929
</script>
-
 
930
 
-
 
931
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
-
 
932
 
-
 
933
## Summary Modal
-
 
934
<div class="modal fade" id="summaryModal" tabindex="-1" role="dialog">
-
 
935
	<div class="modal-dialog" style="width:90%;max-width:1100px;">
-
 
936
		<div class="modal-content">
-
 
937
			<div class="modal-header" style="background:#2c3e50;color:#fff;">
-
 
938
				<button type="button" class="close" data-dismiss="modal" style="color:#fff;opacity:0.8;">&times;
-
 
939
				</button>
-
 
940
				<h4 class="modal-title"><i class="fa fa-bar-chart"></i> Onboarding Timeline Summary</h4>
-
 
941
			</div>
-
 
942
			<div class="modal-body" style="padding:20px;">
-
 
943
				<div class="row">
-
 
944
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
-
 
945
						<canvas id="chartDelaysByStage"></canvas>
-
 
946
					</div>
-
 
947
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
-
 
948
						<canvas id="chartDelaysByTeam"></canvas>
-
 
949
					</div>
-
 
950
				</div>
-
 
951
				<div class="row">
-
 
952
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
-
 
953
						<canvas id="chartDelaySeverity"></canvas>
-
 
954
					</div>
-
 
955
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
-
 
956
						<canvas id="chartLateByStage"></canvas>
-
 
957
					</div>
-
 
958
				</div>
-
 
959
			</div>
-
 
960
			<div class="modal-footer">
-
 
961
				<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
-
 
962
			</div>
-
 
963
		</div>
-
 
964
	</div>
-
 
965
</div>
-
 
966
527
967