Subversion Repositories SmartDukaan

Rev

Rev 37266 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
29061 tejbeer 1
<link href="https://cdn.datatables.net/fixedcolumns/3.3.0/css/fixedColumns.bootstrap.css" rel="stylesheet"/>
2
<style>
35971 aman 3
	.timeline-table {
4
		width: 100%;
5
		border-collapse: collapse;
6
		font-size: 12px;
7
	}
8
 
9
	.timeline-table thead th {
10
		background: #2c3e50;
11
		color: #fff;
12
		padding: 6px 5px;
13
		text-align: center;
14
		font-weight: 600;
15
		font-size: 11px;
16
		border: 1px solid #34495e;
17
		white-space: nowrap;
18
	}
19
 
20
	.timeline-table tbody td {
21
		padding: 5px 4px;
22
		text-align: center;
23
		border: 1px solid #e0e0e0;
24
		vertical-align: middle;
25
		white-space: nowrap;
26
	}
27
 
28
	.timeline-table tbody tr:nth-child(even) {
29
		background-color: #f8f9fa;
30
	}
31
 
32
	.timeline-table tbody tr:hover {
33
		background-color: #eaf2ff;
34
	}
35
 
36
	.status-badge {
37
		display: inline-block;
38
		padding: 2px 6px;
39
		border-radius: 10px;
40
		font-size: 10px;
41
		font-weight: 600;
42
		text-transform: uppercase;
43
	}
44
 
45
	.status-open {
46
		background: #d4edda;
47
		color: #155724;
48
	}
49
 
50
	.status-pending {
51
		background: #fff3cd;
52
		color: #856404;
53
	}
54
 
55
	.status-completed {
56
		background: #cce5ff;
57
		color: #004085;
58
	}
59
 
60
	.status-rejected {
61
		background: #e2e3e5;
62
		color: #383d41;
63
	}
64
 
65
	.tl-cell {
66
		display: inline-flex;
67
		align-items: center;
68
		gap: 5px;
69
	}
70
 
71
	.tl-dot {
72
		height: 8px;
73
		width: 8px;
74
		border-radius: 50%;
75
		display: inline-block;
76
		flex-shrink: 0;
77
	}
78
 
79
	.tl-dot.completed {
80
		background-color: #28a745;
81
	}
82
 
83
	.tl-dot.delay {
84
		background-color: #dc3545;
85
	}
86
 
87
	.tl-dot.wip {
88
		background-color: #ffc107;
89
	}
90
 
91
	.tl-dot.not-started {
92
		background-color: #adb5bd;
93
	}
94
 
95
	.tl-date {
96
		font-size: 10px;
97
		color: #555;
98
	}
99
 
100
	.legend {
101
		display: inline-flex;
102
		gap: 14px;
103
		flex-wrap: wrap;
104
		margin: 0;
105
	}
106
 
107
	.legend-item {
108
		display: flex;
109
		align-items: center;
110
		gap: 5px;
111
		font-size: 11px;
112
		color: #555;
113
	}
114
 
115
	.dataTables_wrapper .top-controls {
116
		display: flex;
117
		align-items: center;
118
		justify-content: space-between;
119
		flex-wrap: nowrap;
120
		padding: 8px 0;
121
	}
122
 
123
	.dataTables_wrapper .top-controls .dataTables_length {
124
		flex-shrink: 0;
125
	}
126
 
127
	.dataTables_wrapper .top-controls .timeline-legend-row {
128
		flex: 1;
129
		display: flex;
130
		justify-content: center;
131
	}
132
 
133
	.dataTables_wrapper .top-controls .dataTables_filter {
134
		flex-shrink: 0;
29061 tejbeer 135
}
136
 
35971 aman 137
	.section-header {
138
		font-size: 11px;
139
		color: #95a5a6;
140
		text-transform: uppercase;
141
		letter-spacing: 0.5px;
142
		padding: 2px 0;
143
	}
29061 tejbeer 144
 
35971 aman 145
	.timeline-table tbody tr {
146
		cursor: pointer;
147
	}
33845 tejus.loha 148
 
35971 aman 149
	.timeline-table tbody tr.row-selected,
150
	.DTFC_LeftBodyWrapper table tbody tr.row-selected {
151
		background-color: #d6eaf8 !important;
152
	}
32550 amit.gupta 153
 
35971 aman 154
	.ob-stepper {
155
		display: flex;
156
		align-items: center;
157
		justify-content: center;
158
		margin: 0 0 20px;
159
		padding: 0;
160
	}
32550 amit.gupta 161
 
35971 aman 162
	.ob-step {
163
		display: flex;
164
		align-items: center;
165
	}
29061 tejbeer 166
 
35971 aman 167
	.ob-step-circle {
168
		width: 28px;
169
		height: 28px;
170
		border-radius: 50%;
171
		display: flex;
172
		align-items: center;
173
		justify-content: center;
174
		font-size: 11px;
175
		font-weight: 700;
176
		color: #fff;
177
		flex-shrink: 0;
178
	}
179
 
180
	.ob-step-circle.completed {
181
		background: #28a745;
182
	}
183
 
184
	.ob-step-circle.inProgress {
185
		background: #007bff;
186
	}
187
 
188
	.ob-step-circle.notStarted {
189
		background: #adb5bd;
190
	}
191
 
192
	.ob-step-line {
193
		width: 50px;
194
		height: 3px;
195
	}
196
 
197
	.ob-step-line.completed {
198
		background: #28a745;
199
	}
200
 
201
	.ob-step-line.inProgress {
202
		background: #007bff;
203
	}
204
 
205
	.ob-step-line.notStarted {
206
		background: #dee2e6;
207
	}
208
 
209
	.ob-step-label {
210
		font-size: 10px;
211
		text-align: center;
212
		margin-top: 4px;
213
		color: #555;
214
	}
215
 
216
	.ob-step-wrap {
217
		display: flex;
218
		flex-direction: column;
219
		align-items: center;
220
	}
221
 
222
	.ob-kv .row {
223
		padding: 4px 0;
224
		border-bottom: 1px solid #f0f0f0;
225
	}
226
 
227
	.ob-kv .text-muted {
228
		font-size: 12px;
229
	}
230
 
231
	.ob-kv .col-sm-8 {
232
		font-size: 12px;
233
	}
234
 
235
	.ob-badge-ok {
236
		display: inline-block;
237
		padding: 2px 8px;
238
		border-radius: 3px;
239
		background: #d4edda;
240
		color: #155724;
241
		font-size: 11px;
242
	}
243
 
244
	.ob-badge-fail {
245
		display: inline-block;
246
		padding: 2px 8px;
247
		border-radius: 3px;
248
		background: #f8d7da;
249
		color: #721c24;
250
		font-size: 11px;
251
	}
252
 
253
	.ob-badge-pending {
254
		display: inline-block;
255
		padding: 2px 8px;
256
		border-radius: 3px;
257
		background: #fff3cd;
258
		color: #856404;
259
		font-size: 11px;
260
	}
261
 
262
	.ob-badge-na {
263
		display: inline-block;
264
		padding: 2px 8px;
265
		border-radius: 3px;
266
		background: #e2e3e5;
267
		color: #6c757d;
268
		font-size: 11px;
269
	}
270
 
271
	a.ob-step-circle {
272
		text-decoration: none;
273
		color: #fff;
274
		cursor: pointer;
275
		transition: transform 0.15s, box-shadow 0.15s;
276
	}
277
 
278
	a.ob-step-circle:hover {
279
		transform: scale(1.2);
280
		box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.3);
281
		color: #fff;
282
	}
283
 
284
	.ob-action-bar {
285
		padding: 8px 10px;
286
		margin-bottom: 12px;
287
		background: #f8f9fa;
288
		border: 1px solid #e9ecef;
289
		border-radius: 4px;
290
		display: flex;
291
		flex-wrap: wrap;
292
		gap: 8px;
293
		align-items: center;
294
	}
295
 
296
	.ob-action-bar .btn-warning {
297
		font-size: 11px;
298
		font-weight: 600;
299
	}
300
 
301
	.ob-action-bar .ob-badge-ok {
302
		font-size: 11px;
303
	}
304
 
305
	.loi-header {
306
		border-bottom: 2px solid #9b59b6;
307
	}
308
 
309
	.onb-header {
310
		border-bottom: 2px solid #2980b9;
311
	}
312
 
313
	.store-header {
314
		border-bottom: 2px solid #27ae60;
315
	}
36242 aman 316
 
317
	/* Per-row "Delay" summary column on the matrix view.
318
	   Surfaces the worst-overdue event for each partner so users can spot the
37110 aman 319
	   biggest blockers without scanning across all 20 stage cells. */
36242 aman 320
	.tl-delay-cell {
321
		text-align: center;
322
		min-width: 110px;
323
		line-height: 1.3;
324
	}
325
 
326
	.tl-delay-chip {
327
		display: inline-block;
328
		padding: 2px 7px;
329
		border-radius: 10px;
330
		font-size: 10px;
331
		font-weight: 700;
332
		color: #fff;
333
	}
334
 
335
	.tl-delay-chip.high {
336
		background: #d9534f;
337
	}
338
 
339
	.tl-delay-chip.med {
340
		background: #f0ad4e;
341
	}
342
 
343
	.tl-delay-chip.low {
344
		background: #f7dc6f;
345
		color: #333;
346
	}
347
 
348
	.tl-delay-team {
349
		display: block;
350
		font-size: 10px;
351
		color: #555;
352
		margin-top: 2px;
353
		max-width: 110px;
354
		overflow: hidden;
355
		text-overflow: ellipsis;
356
		white-space: nowrap;
357
	}
358
 
359
	.tl-delay-none {
360
		color: #ccc;
361
	}
362
 
363
	/* "Show delayed only" filter toggle styling — sits next to the legend in
364
	   the DataTables top-controls flex row. */
365
	.tl-delay-filter {
366
		display: inline-flex;
367
		align-items: center;
368
		gap: 6px;
369
		font-size: 11px;
370
		font-weight: 600;
371
		color: #555;
372
		margin-left: 16px;
373
		cursor: pointer;
374
		user-select: none;
375
	}
376
 
377
	.tl-delay-filter input[type="checkbox"] {
378
		margin: 0;
379
		cursor: pointer;
380
	}
381
 
382
	.tl-delay-filter.active {
383
		color: #d9534f;
384
	}
385
 
386
	/* Tab strip — sits between the breadcrumb and the matrix table.
387
	   Three preset views over the same data: All / Delayed Only / By Team. */
388
	.tl-tab-strip {
389
		display: flex;
390
		align-items: center;
391
		gap: 4px;
392
		padding: 8px 0 12px;
393
		border-bottom: 1px solid #e0e0e0;
394
		margin-bottom: 10px;
395
	}
396
 
397
	.tl-tab {
398
		background: #fff;
399
		border: 1px solid #d0d7de;
400
		border-bottom: none;
401
		border-radius: 4px 4px 0 0;
402
		padding: 8px 16px;
403
		font-size: 12px;
404
		font-weight: 600;
405
		color: #555;
406
		cursor: pointer;
407
		position: relative;
408
		bottom: -1px;
409
		transition: background 0.15s, color 0.15s;
410
	}
411
 
412
	.tl-tab:hover {
413
		background: #f5f7fa;
414
		color: #2c3e50;
415
	}
416
 
417
	.tl-tab.active {
418
		background: #2c3e50;
419
		color: #fff;
420
		border-color: #2c3e50;
421
	}
422
 
423
	.tl-tab i {
424
		margin-right: 5px;
425
	}
426
 
427
	.tl-tab-count {
428
		display: inline-block;
429
		margin-left: 6px;
430
		padding: 1px 7px;
431
		background: #d9534f;
432
		color: #fff;
433
		border-radius: 10px;
434
		font-size: 10px;
435
		font-weight: 700;
436
	}
437
 
438
	.tl-tab.active .tl-tab-count {
439
		background: #fff;
440
		color: #d9534f;
441
	}
442
 
443
	.tl-team-picker {
444
		margin-left: auto;
445
		display: flex;
446
		align-items: center;
447
		gap: 8px;
448
	}
449
 
450
	.tl-team-picker select {
451
		height: 30px;
452
		font-size: 12px;
453
		min-width: 200px;
454
	}
35971 aman 455
</style>
456
 
457
<section class="wrapper">
458
	<div class="row">
459
		<div class="col-lg-12">
460
			<h3 class="page-header" style="color:#2c3e50;">
461
				<i class="icon_document_alt"></i> Partner Onboarding Timeline
462
			</h3>
463
			<ol class="breadcrumb">
464
				<li><i class="fa fa-home"></i><a href="${rc.contextPath}/dashboard">Home</a></li>
465
				<li><i class="icon_document_alt"></i>Onboarding Timeline</li>
466
			</ol>
467
		</div>
468
	</div>
469
 
36242 aman 470
	## Tab strip — three preset views over the same matrix.
471
	## "all" = no filter, "delayed" = only rows with active delays, "team" = delayed + team filter dropdown.
472
	## Each tab is a button that flips JS filter state and re-draws the DataTable.
473
	<div class="col-lg-12 tl-tab-strip">
474
		<button type="button" class="tl-tab active" data-tab="all">
475
			<i class="fa fa-list"></i> All Partners
476
		</button>
477
		<button type="button" class="tl-tab" data-tab="delayed">
478
			<i class="fa fa-exclamation-triangle"></i> Delayed Only
479
			#if($worstDelayMap && $worstDelayMap.size() > 0)
480
				<span class="tl-tab-count">$worstDelayMap.size()</span>
481
			#end
482
		</button>
483
		<button type="button" class="tl-tab" data-tab="team">
484
			<i class="fa fa-users"></i> By Team
485
		</button>
486
		<div class="tl-team-picker" id="tlTeamPicker" style="display:none;">
487
			<select id="tlTeamFilter" class="form-control input-sm">
488
				<option value="">All teams</option>
489
			</select>
490
		</div>
491
		<button type="button" class="btn btn-sm btn-info" id="btnShowSummary" style="margin-left:auto;">
492
			<i class="fa fa-bar-chart"></i> Show Summary
493
		</button>
494
	</div>
495
 
496
	<div class="col-lg-12" style="padding:8px 0 4px;">
497
		<div class="legend" style="justify-content:flex-start;">
498
			<div class="legend-item"><span class="tl-dot completed"></span> Done</div>
499
			<div class="legend-item"><span class="tl-dot completed" style="outline:3px solid #ffe0e0;"></span><span
500
					style="background:#fff0f0;padding:1px 5px;border-radius:3px;margin-left:2px;">Done (crossed TAT)</span>
501
			</div>
502
			<div class="legend-item"><span class="tl-dot delay"></span> Crossed TAT</div>
503
			<div class="legend-item"><span class="tl-dot wip"></span> On Time</div>
504
			<div class="legend-item"><span class="tl-dot not-started"></span> Not Started</div>
505
		</div>
506
	</div>
507
 
35971 aman 508
	<div class="col-lg-12" style="overflow-x:auto;">
509
		<table class="timeline-table" id="storeTimeline">
510
			<thead>
511
			<tr>
512
				<th rowspan="2">ID</th>
513
				<th rowspan="2">Partner</th>
514
				<th rowspan="2">Code</th>
515
				<th rowspan="2">Status</th>
36242 aman 516
				<th rowspan="2" style="background:#c0392b;">Delay</th>
35971 aman 517
				<th colspan="4" style="background:#8e44ad;"><span class="section-header">LOI Process</span></th>
37266 ranu 518
				<th colspan="5" style="background:#2471a3;"><span class="section-header">Onboarding</span></th>
37110 aman 519
				<th colspan="9" style="background:#1e8449;"><span class="section-header">Store Setup</span></th>
35971 aman 520
				<th colspan="3" style="background:#d35400;"><span class="section-header">Launch</span></th>
521
			</tr>
522
			<tr>
523
				<!-- LOI Process -->
524
				<th style="background:#9b59b6;">LOI Form</th>
525
				<th style="background:#9b59b6;">BM Approval</th>
526
				<th style="background:#9b59b6;">Doc Approval</th>
527
				<th style="background:#9b59b6;">Payment Approval</th>
528
				<!-- Onboarding -->
529
				<th style="background:#2980b9;">Onboarding</th>
530
				<th style="background:#2980b9;">Verification</th>
531
				<th style="background:#2980b9;">Store Code</th>
37266 ranu 532
				<th style="background:#2980b9;">RBM Assignment</th>
35971 aman 533
				<th style="background:#2980b9;">Welcome Call</th>
534
				<!-- Store Setup -->
535
				<th style="background:#27ae60;">Recce</th>
536
				<th style="background:#27ae60;">WOD</th>
537
				<th style="background:#27ae60;">Fin Code</th>
36603 aman 538
				<th style="background:#27ae60;">WOD-Fin Mapping</th>
35971 aman 539
				<th style="background:#27ae60;">Branding</th>
540
				<th style="background:#27ae60;">Full Stock</th>
37110 aman 541
				<th style="background:#27ae60;">Agreement E-Sign</th>
35971 aman 542
				<th style="background:#27ae60;">PO Creation</th>
543
				<th style="background:#27ae60;">PO Approval</th>
544
				<!-- Launch -->
545
				<th style="background:#e67e22;">Billing</th>
546
				<th style="background:#e67e22;">Inauguration</th>
547
				<th style="background:#e67e22;">Training</th>
548
			</tr>
549
			</thead>
550
			<tbody>
551
				#foreach($st in $storeTimelines)
36242 aman 552
					## Lookup per-row delay metadata once. partnerDelayMap maps eventName -> DelayReportItem
553
					## (used for hover tooltips on DELAY cells). worstDelay is the single worst-overdue
554
					## item for this partner (drives the new "Delay" column and the row-level filter).
555
					#set($partnerDelayMap = $delayIndex.get($st.getOnboardingId()))
556
					#set($worstDelay = $worstDelayMap.get($st.getOnboardingId()))
557
					#if($worstDelay)
558
					<tr data-has-delay="true" data-delay-team="$!{worstDelay.getResponsibleTeam()}">
559
					#else
560
					<tr data-has-delay="false" data-delay-team="">
561
					#end
35971 aman 562
					<td><strong>$st.getOnboardingId()</strong></td>
563
					<td style="text-align:left;max-width:120px;overflow:hidden;text-overflow:ellipsis;"
564
						title="$st.getOutletName() ($st.getCity())">$st.getOutletName() ($st.getCity())
565
					</td>
566
					#if($st.getCode())
567
						<td>$st.getCode()</td>
568
					#else
569
						<td style="color:#ccc;">-</td>
570
					#end
571
					<td>
572
						#set($statusLower = $st.getStatus().toLowerCase())
573
						#if($statusLower == "open")
574
							<span class="status-badge status-open">$st.getStatus()</span>
575
						#elseif($statusLower == "pending")
576
							<span class="status-badge status-pending">$st.getStatus()</span>
577
						#elseif($statusLower == "completed")
578
							<span class="status-badge status-completed">$st.getStatus()</span>
579
						#else
580
							<span class="status-badge status-rejected">$st.getStatus()</span>
581
						#end
582
					</td>
36242 aman 583
					## --- New "Delay" summary column ---
584
					## Renders the worst-overdue event with a severity-coloured chip + responsible team.
585
					## Sort key (data-order) is daysOverdue so DataTables sorts numerically, not lexically.
586
					#if($worstDelay)
587
						#if($worstDelay.getDaysOverdue() > 7)
588
							#set($delaySeverity = "high")
589
						#elseif($worstDelay.getDaysOverdue() > 3)
590
							#set($delaySeverity = "med")
591
						#else
592
							#set($delaySeverity = "low")
593
						#end
594
						<td class="tl-delay-cell" data-order="$worstDelay.getDaysOverdue()"
595
							title="$worstDelay.getEventName() - planned $worstDelay.getPlannedDate().format($dateFormatter), overdue $worstDelay.getDaysOverdue() day(s), team: $worstDelay.getResponsibleTeam()">
596
							<span class="tl-delay-chip $delaySeverity">$worstDelay.getDaysOverdue()d overdue</span>
597
							<span class="tl-delay-team">$worstDelay.getResponsibleTeam()</span>
598
						</td>
599
					#else
600
						<td class="tl-delay-cell tl-delay-none" data-order="-1">-</td>
601
					#end
602
					## Lookup late-completion metadata for this partner once.
603
					## latePartnerMap maps eventName -> DelayReportItem for events completed
604
					## after their TAT deadline (daysOverdue = how many days late).
605
					#set($latePartnerMap = false)
606
					#if($lateIndex)
607
						#set($latePartnerMap = $lateIndex.get($st.getOnboardingId()))
608
					#end
35971 aman 609
					#if($st.getObtm() && $st.getObtm().size() > 0)
610
						#foreach($obtm in $st.getObtm())
611
							#if($obtm.getStatus().toString() == "COMPLETED")
36242 aman 612
								## Check if this event was completed late (after TAT deadline)
613
								#set($cellLate = false)
614
								#if($latePartnerMap)
615
									#set($cellLate = $latePartnerMap.get($obtm.getTitle().name()))
616
								#end
617
								#if($cellLate)
618
									<td style="background:#fff0f0;"
619
										title="Completed $cellLate.getDaysOverdue() day(s) late (planned: $cellLate.getPlannedDate().format($dateFormatter), actual: $obtm.getCompletedTimestamp().format($dateFormatter))"><span
620
											class="tl-cell"><span
621
											class="tl-dot completed"></span>#if($obtm.getCompletedTimestamp())<span
622
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
623
									</td>
624
								#else
625
									<td><span 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
								#end
35971 aman 630
							#elseif($obtm.getStatus().toString() == "DELAY")
36242 aman 631
								## Look up the matching DelayReportItem to enrich this red cell with
632
								## planned date, days overdue, and responsible team in the title tooltip.
633
								## $obtm.getTitle() is a StoreTimeline enum; .name() gives the string key.
634
								#set($cellDelay = false)
635
								#if($partnerDelayMap)
636
									#set($cellDelay = $partnerDelayMap.get($obtm.getTitle().name()))
637
								#end
638
								#if($cellDelay)
639
									<td style="background:#fff0f0;"
640
										title="Planned: $cellDelay.getPlannedDate().format($dateFormatter) | Overdue: $cellDelay.getDaysOverdue() day(s) | Team: $cellDelay.getResponsibleTeam()"><span
641
											class="tl-cell"><span
642
											class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
643
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
644
									</td>
645
								#else
646
									<td style="background:#fff0f0;"
647
										title="Delayed (planned $!{obtm.getCompletedTimestamp().format($dateFormatter)})"><span
648
											class="tl-cell"><span
649
											class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
650
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
651
									</td>
652
								#end
35971 aman 653
							#elseif($obtm.getStatus().toString() == "WIP")
654
								<td style="background:#fffde7;"><span class="tl-cell"><span
655
										class="tl-dot wip"></span>#if($obtm.getCompletedTimestamp())<span
656
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
657
								</td>
658
							#elseif($obtm.getStatus().toString() == "NOT_STARTED")
659
								<td><span class="tl-cell"><span
660
										class="tl-dot not-started"></span>#if($obtm.getCompletedTimestamp())<span
661
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
662
								</td>
663
							#else
664
								<td style="color:#ccc;">-</td>
665
							#end
666
						#end
667
					#else
37110 aman 668
						#foreach($i in [1..20])
35971 aman 669
							<td style="color:#ccc;">-</td>
670
						#end
671
					#end
672
				</tr>
673
				#end
674
			</tbody>
675
		</table>
676
	</div>
677
 
678
	<div id="timeline-detail-container" style="display:none; margin-top:15px;">
679
		<div class="panel panel-default">
680
			<div class="panel-heading">
681
				<button type="button" class="close" id="closeDetailPanel">&times;</button>
682
				<h4 class="panel-title">
683
					<span id="detail-partner-name"></span>
684
					<small id="detail-partner-code" class="text-muted" style="margin-left:10px;"></small>
685
				</h4>
686
			</div>
687
			<div class="panel-body" id="timeline-detail-body">
688
				<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>
689
			</div>
690
		</div>
691
	</div>
29061 tejbeer 692
</section>
693
 
694
<script type="text/javascript">
35971 aman 695
	$(document).ready(function () {
36242 aman 696
		// Custom DataTables search filter — applies the active tab's filter rules.
697
		// Tab state lives on window so it survives the AJAX re-render of this panel:
698
		//   storeTimelineActiveTab: 'all' | 'delayed' | 'team'
699
		//   storeTimelineTeamFilter: string (only meaningful when tab === 'team')
700
		// Filter is registered once on window to stay idempotent across re-loads.
701
		window.storeTimelineActiveTab = window.storeTimelineActiveTab || 'all';
702
		window.storeTimelineTeamFilter = window.storeTimelineTeamFilter || '';
703
		if (!window.storeTimelineDelayFilterRegistered) {
704
			window.storeTimelineDelayFilterRegistered = true;
705
			$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
706
				if (settings.nTable.id !== 'storeTimeline') return true;
707
				var tab = window.storeTimelineActiveTab;
708
				if (tab === 'all') return true;
709
				var rowNode = settings.aoData[dataIndex].nTr;
710
				// Both 'delayed' and 'team' tabs require an active delay
711
				if ($(rowNode).attr('data-has-delay') !== 'true') return false;
712
				if (tab === 'team' && window.storeTimelineTeamFilter) {
713
					return $(rowNode).attr('data-delay-team') === window.storeTimelineTeamFilter;
714
				}
715
				return true;
716
			});
717
		}
718
 
35971 aman 719
		var dtable = $('#storeTimeline').DataTable({
720
			scrollX: true,
36024 aman 721
			scrollY: '75vh',
722
			scrollCollapse: true,
35971 aman 723
			orderCellsTop: true,
724
			order: [[0, "desc"]],
36024 aman 725
			paging: false,
35971 aman 726
			fixedColumns: {
36242 aman 727
				leftColumns: 5
35971 aman 728
			},
36024 aman 729
			dom: '<"top-controls"f>rt<"bottom"i>',
35971 aman 730
			language: {
36024 aman 731
				search: "Search Partners:"
35971 aman 732
			}
733
		});
29061 tejbeer 734
 
36242 aman 735
		// Legend is now rendered as static HTML above the table (not inside DataTables controls).
29061 tejbeer 736
 
36242 aman 737
		// Populate the team dropdown from the unique data-delay-team values on rows.
738
		// Only delayed rows have a team set, so this naturally excludes empty values.
739
		var teamSet = {};
740
		$('#storeTimeline tbody tr[data-has-delay="true"]').each(function () {
741
			var team = $(this).attr('data-delay-team');
742
			if (team) teamSet[team] = true;
743
		});
744
		var teamSelect = $('#tlTeamFilter');
745
		Object.keys(teamSet).sort().forEach(function (team) {
746
			teamSelect.append($('<option></option>').val(team).text(team));
747
		});
748
 
749
		// applyTab — single source of truth for switching tabs.
750
		// Updates window state, button highlighting, dropdown visibility,
751
		// sort order, and triggers a DataTable redraw.
752
		function applyTab(tab) {
753
			window.storeTimelineActiveTab = tab;
754
			$('.tl-tab').removeClass('active');
755
			$('.tl-tab[data-tab="' + tab + '"]').addClass('active');
756
			$('#tlTeamPicker').toggle(tab === 'team');
757
			if (tab !== 'team') {
758
				window.storeTimelineTeamFilter = '';
759
				teamSelect.val('');
760
			}
761
			dtable.order([0, 'desc']).draw();
762
		}
763
 
764
		// Tab click → apply the corresponding filter view
765
		$('.tl-tab').on('click', function () {
766
			applyTab($(this).data('tab'));
767
		});
768
 
769
		// Team dropdown change → re-apply the team filter (only meaningful on the By Team tab)
770
		teamSelect.on('change', function () {
771
			window.storeTimelineTeamFilter = $(this).val();
772
			dtable.draw();
773
		});
774
 
775
		// Honor an optional defaultTab model attribute set by the controller.
776
		// Used by /partnerDelayPanel (which now delegates here) to land users
777
		// on the Delayed Only tab without an extra click.
778
		var initialTab = '$!{defaultTab}';
779
		if (initialTab === 'delayed' || initialTab === 'team') {
780
			applyTab(initialTab);
781
		}
782
 
35971 aman 783
		// Row click -> load detail panel
784
		$('#storeTimeline tbody').on('click', 'tr', function () {
785
			var $row = $(this);
786
			var onboardingId = $.trim($row.find('td:first').text());
787
			if (!onboardingId || isNaN(onboardingId)) return;
788
 
789
			// Highlight selected row (both main table and fixed-column clone)
790
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
791
			var rowIndex = dtable.row($row).index();
792
			$('#storeTimeline tbody tr').eq(rowIndex).addClass('row-selected');
793
			$('.DTFC_LeftBodyWrapper table tbody tr').eq(rowIndex).addClass('row-selected');
794
 
795
			var partnerName = $.trim($row.find('td:eq(1)').text());
796
			var partnerCode = $.trim($row.find('td:eq(2)').text());
797
 
798
			$('#detail-partner-name').text(partnerName);
799
			$('#detail-partner-code').text(partnerCode !== '-' ? partnerCode : '');
800
			$('#timeline-detail-body').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
801
			$('#storeTimeline_wrapper').hide();
802
			$('#timeline-detail-container').show();
803
 
804
			$.ajax({
805
				url: '${rc.contextPath}/partnerTimelineDetail',
806
				data: {onboardingId: onboardingId},
807
				success: function (html) {
808
					$('#timeline-detail-body').html(html);
809
				},
810
				error: function () {
811
					$('#timeline-detail-body').html('<div class="alert alert-danger">Failed to load details.</div>');
812
				}
813
			});
814
 
815
			$('html, body').animate({scrollTop: $('#timeline-detail-container').offset().top - 60}, 300);
816
		});
817
 
818
		// Close detail panel
37498 amit 819
		// Fragment is re-injected with .html(), which re-runs this script; clear the
820
		// namespace first so handlers replace rather than stack.
821
		$(document).off('.onboardingtimeline');
822
		$(document).on('click.onboardingtimeline', '#closeDetailPanel', function () {
35971 aman 823
			$('#timeline-detail-container').hide();
824
			$('#storeTimeline_wrapper').show();
825
			dtable.columns.adjust();
826
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
827
		});
36242 aman 828
 
829
		// Show Summary modal — renders Chart.js charts on first open
830
		var chartsRendered = false;
831
		$('#btnShowSummary').on('click', function () {
832
			$('#summaryModal').modal('show');
833
			if (chartsRendered) return;
834
			chartsRendered = true;
835
 
836
			// --- Chart 1: Delays by Stage (horizontal bar) ---
837
			var stageLabels = [#foreach($e in $delaysByStage.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
838
			var stageCounts = [#foreach($e in $delaysByStage.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
839
			new Chart(document.getElementById('chartDelaysByStage'), {
840
				type: 'bar',
841
				data: {
842
					labels: stageLabels,
843
					datasets: [{
844
						label: 'Delayed Partners',
845
						data: stageCounts,
846
						backgroundColor: '#e74c3c'
847
					}]
848
				},
849
				options: {
850
					indexAxis: 'y',
851
					responsive: true,
852
					maintainAspectRatio: false,
853
					plugins: {
854
						legend: {display: false},
855
						title: {display: true, text: 'Active Delays by Stage ($totalDelays total)', font: {size: 14}}
856
					},
857
					scales: {x: {beginAtZero: true, ticks: {stepSize: 1}}}
858
				}
859
			});
860
 
861
			// --- Chart 2: Delays by Team (donut) ---
862
			var teamLabels = [#foreach($e in $delaysByTeam.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
863
			var teamCounts = [#foreach($e in $delaysByTeam.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
864
			var teamColors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#e67e22', '#34495e'];
865
			new Chart(document.getElementById('chartDelaysByTeam'), {
866
				type: 'doughnut',
867
				data: {
868
					labels: teamLabels,
869
					datasets: [{data: teamCounts, backgroundColor: teamColors.slice(0, teamLabels.length)}]
870
				},
871
				options: {
872
					responsive: true,
873
					maintainAspectRatio: false,
874
					plugins: {
875
						title: {display: true, text: 'Delays by Responsible Team', font: {size: 14}},
876
						legend: {position: 'right'}
877
					}
878
				}
879
			});
880
 
881
			// --- Chart 3: Delay Severity (donut) ---
882
			new Chart(document.getElementById('chartDelaySeverity'), {
883
				type: 'doughnut',
884
				data: {
885
					labels: ['Critical (>7 days)', 'Medium (3-7 days)', 'Low (\u22643 days)'],
886
					datasets: [{
887
						data: [$severityCritical, $severityMedium, $severityLow],
888
						backgroundColor: ['#d9534f', '#f0ad4e', '#f7dc6f']
889
					}]
890
				},
891
				options: {
892
					responsive: true,
893
					maintainAspectRatio: false,
894
					plugins: {
895
						title: {display: true, text: 'Delay Severity Breakdown', font: {size: 14}},
896
						legend: {position: 'right'}
897
					}
898
				}
899
			});
900
 
901
			// --- Chart 4: Late Completions by Stage (horizontal bar) ---
902
			var lateLabels = [#foreach($e in $lateByStage.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
903
			var lateCounts = [#foreach($e in $lateByStage.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
904
			new Chart(document.getElementById('chartLateByStage'), {
905
				type: 'bar',
906
				data: {
907
					labels: lateLabels,
908
					datasets: [{
909
						label: 'Late Completions',
910
						data: lateCounts,
911
						backgroundColor: '#e67e22'
912
					}]
913
				},
914
				options: {
915
					indexAxis: 'y',
916
					responsive: true,
917
					maintainAspectRatio: false,
918
					plugins: {
919
						legend: {display: false},
920
						title: {display: true, text: 'Late Completions by Stage ($totalLate total)', font: {size: 14}}
921
					},
922
					scales: {x: {beginAtZero: true, ticks: {stepSize: 1}}}
923
				}
924
			});
925
		});
35971 aman 926
	});
36242 aman 927
</script>
928
 
929
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
930
 
931
## Summary Modal
932
<div class="modal fade" id="summaryModal" tabindex="-1" role="dialog">
933
	<div class="modal-dialog" style="width:90%;max-width:1100px;">
934
		<div class="modal-content">
935
			<div class="modal-header" style="background:#2c3e50;color:#fff;">
936
				<button type="button" class="close" data-dismiss="modal" style="color:#fff;opacity:0.8;">&times;
937
				</button>
938
				<h4 class="modal-title"><i class="fa fa-bar-chart"></i> Onboarding Timeline Summary</h4>
939
			</div>
940
			<div class="modal-body" style="padding:20px;">
941
				<div class="row">
942
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
943
						<canvas id="chartDelaysByStage"></canvas>
944
					</div>
945
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
946
						<canvas id="chartDelaysByTeam"></canvas>
947
					</div>
948
				</div>
949
				<div class="row">
950
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
951
						<canvas id="chartDelaySeverity"></canvas>
952
					</div>
953
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
954
						<canvas id="chartLateByStage"></canvas>
955
					</div>
956
				</div>
957
			</div>
958
			<div class="modal-footer">
959
				<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
960
			</div>
961
		</div>
962
	</div>
963
</div>