Subversion Repositories SmartDukaan

Rev

Rev 36603 | 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>
518
				<th colspan="4" 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>
532
				<th style="background:#2980b9;">Welcome Call</th>
533
				<!-- Store Setup -->
534
				<th style="background:#27ae60;">Recce</th>
535
				<th style="background:#27ae60;">WOD</th>
536
				<th style="background:#27ae60;">Fin Code</th>
36603 aman 537
				<th style="background:#27ae60;">WOD-Fin Mapping</th>
35971 aman 538
				<th style="background:#27ae60;">Branding</th>
539
				<th style="background:#27ae60;">Full Stock</th>
37110 aman 540
				<th style="background:#27ae60;">Agreement E-Sign</th>
35971 aman 541
				<th style="background:#27ae60;">PO Creation</th>
542
				<th style="background:#27ae60;">PO Approval</th>
543
				<!-- Launch -->
544
				<th style="background:#e67e22;">Billing</th>
545
				<th style="background:#e67e22;">Inauguration</th>
546
				<th style="background:#e67e22;">Training</th>
547
			</tr>
548
			</thead>
549
			<tbody>
550
				#foreach($st in $storeTimelines)
36242 aman 551
					## Lookup per-row delay metadata once. partnerDelayMap maps eventName -> DelayReportItem
552
					## (used for hover tooltips on DELAY cells). worstDelay is the single worst-overdue
553
					## item for this partner (drives the new "Delay" column and the row-level filter).
554
					#set($partnerDelayMap = $delayIndex.get($st.getOnboardingId()))
555
					#set($worstDelay = $worstDelayMap.get($st.getOnboardingId()))
556
					#if($worstDelay)
557
					<tr data-has-delay="true" data-delay-team="$!{worstDelay.getResponsibleTeam()}">
558
					#else
559
					<tr data-has-delay="false" data-delay-team="">
560
					#end
35971 aman 561
					<td><strong>$st.getOnboardingId()</strong></td>
562
					<td style="text-align:left;max-width:120px;overflow:hidden;text-overflow:ellipsis;"
563
						title="$st.getOutletName() ($st.getCity())">$st.getOutletName() ($st.getCity())
564
					</td>
565
					#if($st.getCode())
566
						<td>$st.getCode()</td>
567
					#else
568
						<td style="color:#ccc;">-</td>
569
					#end
570
					<td>
571
						#set($statusLower = $st.getStatus().toLowerCase())
572
						#if($statusLower == "open")
573
							<span class="status-badge status-open">$st.getStatus()</span>
574
						#elseif($statusLower == "pending")
575
							<span class="status-badge status-pending">$st.getStatus()</span>
576
						#elseif($statusLower == "completed")
577
							<span class="status-badge status-completed">$st.getStatus()</span>
578
						#else
579
							<span class="status-badge status-rejected">$st.getStatus()</span>
580
						#end
581
					</td>
36242 aman 582
					## --- New "Delay" summary column ---
583
					## Renders the worst-overdue event with a severity-coloured chip + responsible team.
584
					## Sort key (data-order) is daysOverdue so DataTables sorts numerically, not lexically.
585
					#if($worstDelay)
586
						#if($worstDelay.getDaysOverdue() > 7)
587
							#set($delaySeverity = "high")
588
						#elseif($worstDelay.getDaysOverdue() > 3)
589
							#set($delaySeverity = "med")
590
						#else
591
							#set($delaySeverity = "low")
592
						#end
593
						<td class="tl-delay-cell" data-order="$worstDelay.getDaysOverdue()"
594
							title="$worstDelay.getEventName() - planned $worstDelay.getPlannedDate().format($dateFormatter), overdue $worstDelay.getDaysOverdue() day(s), team: $worstDelay.getResponsibleTeam()">
595
							<span class="tl-delay-chip $delaySeverity">$worstDelay.getDaysOverdue()d overdue</span>
596
							<span class="tl-delay-team">$worstDelay.getResponsibleTeam()</span>
597
						</td>
598
					#else
599
						<td class="tl-delay-cell tl-delay-none" data-order="-1">-</td>
600
					#end
601
					## Lookup late-completion metadata for this partner once.
602
					## latePartnerMap maps eventName -> DelayReportItem for events completed
603
					## after their TAT deadline (daysOverdue = how many days late).
604
					#set($latePartnerMap = false)
605
					#if($lateIndex)
606
						#set($latePartnerMap = $lateIndex.get($st.getOnboardingId()))
607
					#end
35971 aman 608
					#if($st.getObtm() && $st.getObtm().size() > 0)
609
						#foreach($obtm in $st.getObtm())
610
							#if($obtm.getStatus().toString() == "COMPLETED")
36242 aman 611
								## Check if this event was completed late (after TAT deadline)
612
								#set($cellLate = false)
613
								#if($latePartnerMap)
614
									#set($cellLate = $latePartnerMap.get($obtm.getTitle().name()))
615
								#end
616
								#if($cellLate)
617
									<td style="background:#fff0f0;"
618
										title="Completed $cellLate.getDaysOverdue() day(s) late (planned: $cellLate.getPlannedDate().format($dateFormatter), actual: $obtm.getCompletedTimestamp().format($dateFormatter))"><span
619
											class="tl-cell"><span
620
											class="tl-dot completed"></span>#if($obtm.getCompletedTimestamp())<span
621
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
622
									</td>
623
								#else
624
									<td><span class="tl-cell"><span
625
											class="tl-dot completed"></span>#if($obtm.getCompletedTimestamp())<span
626
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
627
									</td>
628
								#end
35971 aman 629
							#elseif($obtm.getStatus().toString() == "DELAY")
36242 aman 630
								## Look up the matching DelayReportItem to enrich this red cell with
631
								## planned date, days overdue, and responsible team in the title tooltip.
632
								## $obtm.getTitle() is a StoreTimeline enum; .name() gives the string key.
633
								#set($cellDelay = false)
634
								#if($partnerDelayMap)
635
									#set($cellDelay = $partnerDelayMap.get($obtm.getTitle().name()))
636
								#end
637
								#if($cellDelay)
638
									<td style="background:#fff0f0;"
639
										title="Planned: $cellDelay.getPlannedDate().format($dateFormatter) | Overdue: $cellDelay.getDaysOverdue() day(s) | Team: $cellDelay.getResponsibleTeam()"><span
640
											class="tl-cell"><span
641
											class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
642
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
643
									</td>
644
								#else
645
									<td style="background:#fff0f0;"
646
										title="Delayed (planned $!{obtm.getCompletedTimestamp().format($dateFormatter)})"><span
647
											class="tl-cell"><span
648
											class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
649
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
650
									</td>
651
								#end
35971 aman 652
							#elseif($obtm.getStatus().toString() == "WIP")
653
								<td style="background:#fffde7;"><span class="tl-cell"><span
654
										class="tl-dot wip"></span>#if($obtm.getCompletedTimestamp())<span
655
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
656
								</td>
657
							#elseif($obtm.getStatus().toString() == "NOT_STARTED")
658
								<td><span class="tl-cell"><span
659
										class="tl-dot not-started"></span>#if($obtm.getCompletedTimestamp())<span
660
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
661
								</td>
662
							#else
663
								<td style="color:#ccc;">-</td>
664
							#end
665
						#end
666
					#else
37110 aman 667
						#foreach($i in [1..20])
35971 aman 668
							<td style="color:#ccc;">-</td>
669
						#end
670
					#end
671
				</tr>
672
				#end
673
			</tbody>
674
		</table>
675
	</div>
676
 
677
	<div id="timeline-detail-container" style="display:none; margin-top:15px;">
678
		<div class="panel panel-default">
679
			<div class="panel-heading">
680
				<button type="button" class="close" id="closeDetailPanel">&times;</button>
681
				<h4 class="panel-title">
682
					<span id="detail-partner-name"></span>
683
					<small id="detail-partner-code" class="text-muted" style="margin-left:10px;"></small>
684
				</h4>
685
			</div>
686
			<div class="panel-body" id="timeline-detail-body">
687
				<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>
688
			</div>
689
		</div>
690
	</div>
29061 tejbeer 691
</section>
692
 
693
<script type="text/javascript">
35971 aman 694
	$(document).ready(function () {
36242 aman 695
		// Custom DataTables search filter — applies the active tab's filter rules.
696
		// Tab state lives on window so it survives the AJAX re-render of this panel:
697
		//   storeTimelineActiveTab: 'all' | 'delayed' | 'team'
698
		//   storeTimelineTeamFilter: string (only meaningful when tab === 'team')
699
		// Filter is registered once on window to stay idempotent across re-loads.
700
		window.storeTimelineActiveTab = window.storeTimelineActiveTab || 'all';
701
		window.storeTimelineTeamFilter = window.storeTimelineTeamFilter || '';
702
		if (!window.storeTimelineDelayFilterRegistered) {
703
			window.storeTimelineDelayFilterRegistered = true;
704
			$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
705
				if (settings.nTable.id !== 'storeTimeline') return true;
706
				var tab = window.storeTimelineActiveTab;
707
				if (tab === 'all') return true;
708
				var rowNode = settings.aoData[dataIndex].nTr;
709
				// Both 'delayed' and 'team' tabs require an active delay
710
				if ($(rowNode).attr('data-has-delay') !== 'true') return false;
711
				if (tab === 'team' && window.storeTimelineTeamFilter) {
712
					return $(rowNode).attr('data-delay-team') === window.storeTimelineTeamFilter;
713
				}
714
				return true;
715
			});
716
		}
717
 
35971 aman 718
		var dtable = $('#storeTimeline').DataTable({
719
			scrollX: true,
36024 aman 720
			scrollY: '75vh',
721
			scrollCollapse: true,
35971 aman 722
			orderCellsTop: true,
723
			order: [[0, "desc"]],
36024 aman 724
			paging: false,
35971 aman 725
			fixedColumns: {
36242 aman 726
				leftColumns: 5
35971 aman 727
			},
36024 aman 728
			dom: '<"top-controls"f>rt<"bottom"i>',
35971 aman 729
			language: {
36024 aman 730
				search: "Search Partners:"
35971 aman 731
			}
732
		});
29061 tejbeer 733
 
36242 aman 734
		// Legend is now rendered as static HTML above the table (not inside DataTables controls).
29061 tejbeer 735
 
36242 aman 736
		// Populate the team dropdown from the unique data-delay-team values on rows.
737
		// Only delayed rows have a team set, so this naturally excludes empty values.
738
		var teamSet = {};
739
		$('#storeTimeline tbody tr[data-has-delay="true"]').each(function () {
740
			var team = $(this).attr('data-delay-team');
741
			if (team) teamSet[team] = true;
742
		});
743
		var teamSelect = $('#tlTeamFilter');
744
		Object.keys(teamSet).sort().forEach(function (team) {
745
			teamSelect.append($('<option></option>').val(team).text(team));
746
		});
747
 
748
		// applyTab — single source of truth for switching tabs.
749
		// Updates window state, button highlighting, dropdown visibility,
750
		// sort order, and triggers a DataTable redraw.
751
		function applyTab(tab) {
752
			window.storeTimelineActiveTab = tab;
753
			$('.tl-tab').removeClass('active');
754
			$('.tl-tab[data-tab="' + tab + '"]').addClass('active');
755
			$('#tlTeamPicker').toggle(tab === 'team');
756
			if (tab !== 'team') {
757
				window.storeTimelineTeamFilter = '';
758
				teamSelect.val('');
759
			}
760
			dtable.order([0, 'desc']).draw();
761
		}
762
 
763
		// Tab click → apply the corresponding filter view
764
		$('.tl-tab').on('click', function () {
765
			applyTab($(this).data('tab'));
766
		});
767
 
768
		// Team dropdown change → re-apply the team filter (only meaningful on the By Team tab)
769
		teamSelect.on('change', function () {
770
			window.storeTimelineTeamFilter = $(this).val();
771
			dtable.draw();
772
		});
773
 
774
		// Honor an optional defaultTab model attribute set by the controller.
775
		// Used by /partnerDelayPanel (which now delegates here) to land users
776
		// on the Delayed Only tab without an extra click.
777
		var initialTab = '$!{defaultTab}';
778
		if (initialTab === 'delayed' || initialTab === 'team') {
779
			applyTab(initialTab);
780
		}
781
 
35971 aman 782
		// Row click -> load detail panel
783
		$('#storeTimeline tbody').on('click', 'tr', function () {
784
			var $row = $(this);
785
			var onboardingId = $.trim($row.find('td:first').text());
786
			if (!onboardingId || isNaN(onboardingId)) return;
787
 
788
			// Highlight selected row (both main table and fixed-column clone)
789
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
790
			var rowIndex = dtable.row($row).index();
791
			$('#storeTimeline tbody tr').eq(rowIndex).addClass('row-selected');
792
			$('.DTFC_LeftBodyWrapper table tbody tr').eq(rowIndex).addClass('row-selected');
793
 
794
			var partnerName = $.trim($row.find('td:eq(1)').text());
795
			var partnerCode = $.trim($row.find('td:eq(2)').text());
796
 
797
			$('#detail-partner-name').text(partnerName);
798
			$('#detail-partner-code').text(partnerCode !== '-' ? partnerCode : '');
799
			$('#timeline-detail-body').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
800
			$('#storeTimeline_wrapper').hide();
801
			$('#timeline-detail-container').show();
802
 
803
			$.ajax({
804
				url: '${rc.contextPath}/partnerTimelineDetail',
805
				data: {onboardingId: onboardingId},
806
				success: function (html) {
807
					$('#timeline-detail-body').html(html);
808
				},
809
				error: function () {
810
					$('#timeline-detail-body').html('<div class="alert alert-danger">Failed to load details.</div>');
811
				}
812
			});
813
 
814
			$('html, body').animate({scrollTop: $('#timeline-detail-container').offset().top - 60}, 300);
815
		});
816
 
817
		// Close detail panel
818
		$(document).on('click', '#closeDetailPanel', function () {
819
			$('#timeline-detail-container').hide();
820
			$('#storeTimeline_wrapper').show();
821
			dtable.columns.adjust();
822
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
823
		});
36242 aman 824
 
825
		// Show Summary modal — renders Chart.js charts on first open
826
		var chartsRendered = false;
827
		$('#btnShowSummary').on('click', function () {
828
			$('#summaryModal').modal('show');
829
			if (chartsRendered) return;
830
			chartsRendered = true;
831
 
832
			// --- Chart 1: Delays by Stage (horizontal bar) ---
833
			var stageLabels = [#foreach($e in $delaysByStage.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
834
			var stageCounts = [#foreach($e in $delaysByStage.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
835
			new Chart(document.getElementById('chartDelaysByStage'), {
836
				type: 'bar',
837
				data: {
838
					labels: stageLabels,
839
					datasets: [{
840
						label: 'Delayed Partners',
841
						data: stageCounts,
842
						backgroundColor: '#e74c3c'
843
					}]
844
				},
845
				options: {
846
					indexAxis: 'y',
847
					responsive: true,
848
					maintainAspectRatio: false,
849
					plugins: {
850
						legend: {display: false},
851
						title: {display: true, text: 'Active Delays by Stage ($totalDelays total)', font: {size: 14}}
852
					},
853
					scales: {x: {beginAtZero: true, ticks: {stepSize: 1}}}
854
				}
855
			});
856
 
857
			// --- Chart 2: Delays by Team (donut) ---
858
			var teamLabels = [#foreach($e in $delaysByTeam.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
859
			var teamCounts = [#foreach($e in $delaysByTeam.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
860
			var teamColors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#e67e22', '#34495e'];
861
			new Chart(document.getElementById('chartDelaysByTeam'), {
862
				type: 'doughnut',
863
				data: {
864
					labels: teamLabels,
865
					datasets: [{data: teamCounts, backgroundColor: teamColors.slice(0, teamLabels.length)}]
866
				},
867
				options: {
868
					responsive: true,
869
					maintainAspectRatio: false,
870
					plugins: {
871
						title: {display: true, text: 'Delays by Responsible Team', font: {size: 14}},
872
						legend: {position: 'right'}
873
					}
874
				}
875
			});
876
 
877
			// --- Chart 3: Delay Severity (donut) ---
878
			new Chart(document.getElementById('chartDelaySeverity'), {
879
				type: 'doughnut',
880
				data: {
881
					labels: ['Critical (>7 days)', 'Medium (3-7 days)', 'Low (\u22643 days)'],
882
					datasets: [{
883
						data: [$severityCritical, $severityMedium, $severityLow],
884
						backgroundColor: ['#d9534f', '#f0ad4e', '#f7dc6f']
885
					}]
886
				},
887
				options: {
888
					responsive: true,
889
					maintainAspectRatio: false,
890
					plugins: {
891
						title: {display: true, text: 'Delay Severity Breakdown', font: {size: 14}},
892
						legend: {position: 'right'}
893
					}
894
				}
895
			});
896
 
897
			// --- Chart 4: Late Completions by Stage (horizontal bar) ---
898
			var lateLabels = [#foreach($e in $lateByStage.entrySet())'$e.getKey()'#if($foreach.hasNext),#end#end];
899
			var lateCounts = [#foreach($e in $lateByStage.entrySet())$e.getValue()#if($foreach.hasNext),#end#end];
900
			new Chart(document.getElementById('chartLateByStage'), {
901
				type: 'bar',
902
				data: {
903
					labels: lateLabels,
904
					datasets: [{
905
						label: 'Late Completions',
906
						data: lateCounts,
907
						backgroundColor: '#e67e22'
908
					}]
909
				},
910
				options: {
911
					indexAxis: 'y',
912
					responsive: true,
913
					maintainAspectRatio: false,
914
					plugins: {
915
						legend: {display: false},
916
						title: {display: true, text: 'Late Completions by Stage ($totalLate total)', font: {size: 14}}
917
					},
918
					scales: {x: {beginAtZero: true, ticks: {stepSize: 1}}}
919
				}
920
			});
921
		});
35971 aman 922
	});
36242 aman 923
</script>
924
 
925
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
926
 
927
## Summary Modal
928
<div class="modal fade" id="summaryModal" tabindex="-1" role="dialog">
929
	<div class="modal-dialog" style="width:90%;max-width:1100px;">
930
		<div class="modal-content">
931
			<div class="modal-header" style="background:#2c3e50;color:#fff;">
932
				<button type="button" class="close" data-dismiss="modal" style="color:#fff;opacity:0.8;">&times;
933
				</button>
934
				<h4 class="modal-title"><i class="fa fa-bar-chart"></i> Onboarding Timeline Summary</h4>
935
			</div>
936
			<div class="modal-body" style="padding:20px;">
937
				<div class="row">
938
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
939
						<canvas id="chartDelaysByStage"></canvas>
940
					</div>
941
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
942
						<canvas id="chartDelaysByTeam"></canvas>
943
					</div>
944
				</div>
945
				<div class="row">
946
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
947
						<canvas id="chartDelaySeverity"></canvas>
948
					</div>
949
					<div class="col-md-6" style="height:320px;margin-bottom:20px;">
950
						<canvas id="chartLateByStage"></canvas>
951
					</div>
952
				</div>
953
			</div>
954
			<div class="modal-footer">
955
				<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
956
			</div>
957
		</div>
958
	</div>
959
</div>