Subversion Repositories SmartDukaan

Rev

Rev 36024 | Rev 36368 | Go to most recent revision | 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-hold {
61
		background: #f8d7da;
62
		color: #721c24;
63
	}
64
 
65
	.status-rejected {
66
		background: #e2e3e5;
67
		color: #383d41;
68
	}
69
 
70
	.tl-cell {
71
		display: inline-flex;
72
		align-items: center;
73
		gap: 5px;
74
	}
75
 
76
	.tl-dot {
77
		height: 8px;
78
		width: 8px;
79
		border-radius: 50%;
80
		display: inline-block;
81
		flex-shrink: 0;
82
	}
83
 
84
	.tl-dot.completed {
85
		background-color: #28a745;
86
	}
87
 
88
	.tl-dot.delay {
89
		background-color: #dc3545;
90
	}
91
 
92
	.tl-dot.wip {
93
		background-color: #ffc107;
94
	}
95
 
96
	.tl-dot.not-started {
97
		background-color: #adb5bd;
98
	}
99
 
100
	.tl-date {
101
		font-size: 10px;
102
		color: #555;
103
	}
104
 
105
	.legend {
106
		display: inline-flex;
107
		gap: 14px;
108
		flex-wrap: wrap;
109
		margin: 0;
110
	}
111
 
112
	.legend-item {
113
		display: flex;
114
		align-items: center;
115
		gap: 5px;
116
		font-size: 11px;
117
		color: #555;
118
	}
119
 
120
	.dataTables_wrapper .top-controls {
121
		display: flex;
122
		align-items: center;
123
		justify-content: space-between;
124
		flex-wrap: nowrap;
125
		padding: 8px 0;
126
	}
127
 
128
	.dataTables_wrapper .top-controls .dataTables_length {
129
		flex-shrink: 0;
130
	}
131
 
132
	.dataTables_wrapper .top-controls .timeline-legend-row {
133
		flex: 1;
134
		display: flex;
135
		justify-content: center;
136
	}
137
 
138
	.dataTables_wrapper .top-controls .dataTables_filter {
139
		flex-shrink: 0;
29061 tejbeer 140
}
141
 
35971 aman 142
	.section-header {
143
		font-size: 11px;
144
		color: #95a5a6;
145
		text-transform: uppercase;
146
		letter-spacing: 0.5px;
147
		padding: 2px 0;
148
	}
29061 tejbeer 149
 
35971 aman 150
	.timeline-table tbody tr {
151
		cursor: pointer;
152
	}
33845 tejus.loha 153
 
35971 aman 154
	.timeline-table tbody tr.row-selected,
155
	.DTFC_LeftBodyWrapper table tbody tr.row-selected {
156
		background-color: #d6eaf8 !important;
157
	}
32550 amit.gupta 158
 
35971 aman 159
	.ob-stepper {
160
		display: flex;
161
		align-items: center;
162
		justify-content: center;
163
		margin: 0 0 20px;
164
		padding: 0;
165
	}
32550 amit.gupta 166
 
35971 aman 167
	.ob-step {
168
		display: flex;
169
		align-items: center;
170
	}
29061 tejbeer 171
 
35971 aman 172
	.ob-step-circle {
173
		width: 28px;
174
		height: 28px;
175
		border-radius: 50%;
176
		display: flex;
177
		align-items: center;
178
		justify-content: center;
179
		font-size: 11px;
180
		font-weight: 700;
181
		color: #fff;
182
		flex-shrink: 0;
183
	}
184
 
185
	.ob-step-circle.completed {
186
		background: #28a745;
187
	}
188
 
189
	.ob-step-circle.inProgress {
190
		background: #007bff;
191
	}
192
 
193
	.ob-step-circle.notStarted {
194
		background: #adb5bd;
195
	}
196
 
197
	.ob-step-line {
198
		width: 50px;
199
		height: 3px;
200
	}
201
 
202
	.ob-step-line.completed {
203
		background: #28a745;
204
	}
205
 
206
	.ob-step-line.inProgress {
207
		background: #007bff;
208
	}
209
 
210
	.ob-step-line.notStarted {
211
		background: #dee2e6;
212
	}
213
 
214
	.ob-step-label {
215
		font-size: 10px;
216
		text-align: center;
217
		margin-top: 4px;
218
		color: #555;
219
	}
220
 
221
	.ob-step-wrap {
222
		display: flex;
223
		flex-direction: column;
224
		align-items: center;
225
	}
226
 
227
	.ob-kv .row {
228
		padding: 4px 0;
229
		border-bottom: 1px solid #f0f0f0;
230
	}
231
 
232
	.ob-kv .text-muted {
233
		font-size: 12px;
234
	}
235
 
236
	.ob-kv .col-sm-8 {
237
		font-size: 12px;
238
	}
239
 
240
	.ob-badge-ok {
241
		display: inline-block;
242
		padding: 2px 8px;
243
		border-radius: 3px;
244
		background: #d4edda;
245
		color: #155724;
246
		font-size: 11px;
247
	}
248
 
249
	.ob-badge-fail {
250
		display: inline-block;
251
		padding: 2px 8px;
252
		border-radius: 3px;
253
		background: #f8d7da;
254
		color: #721c24;
255
		font-size: 11px;
256
	}
257
 
258
	.ob-badge-pending {
259
		display: inline-block;
260
		padding: 2px 8px;
261
		border-radius: 3px;
262
		background: #fff3cd;
263
		color: #856404;
264
		font-size: 11px;
265
	}
266
 
267
	.ob-badge-na {
268
		display: inline-block;
269
		padding: 2px 8px;
270
		border-radius: 3px;
271
		background: #e2e3e5;
272
		color: #6c757d;
273
		font-size: 11px;
274
	}
275
 
276
	a.ob-step-circle {
277
		text-decoration: none;
278
		color: #fff;
279
		cursor: pointer;
280
		transition: transform 0.15s, box-shadow 0.15s;
281
	}
282
 
283
	a.ob-step-circle:hover {
284
		transform: scale(1.2);
285
		box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.3);
286
		color: #fff;
287
	}
288
 
289
	.ob-action-bar {
290
		padding: 8px 10px;
291
		margin-bottom: 12px;
292
		background: #f8f9fa;
293
		border: 1px solid #e9ecef;
294
		border-radius: 4px;
295
		display: flex;
296
		flex-wrap: wrap;
297
		gap: 8px;
298
		align-items: center;
299
	}
300
 
301
	.ob-action-bar .btn-warning {
302
		font-size: 11px;
303
		font-weight: 600;
304
	}
305
 
306
	.ob-action-bar .ob-badge-ok {
307
		font-size: 11px;
308
	}
309
 
310
	.loi-header {
311
		border-bottom: 2px solid #9b59b6;
312
	}
313
 
314
	.onb-header {
315
		border-bottom: 2px solid #2980b9;
316
	}
317
 
318
	.store-header {
319
		border-bottom: 2px solid #27ae60;
320
	}
36242 aman 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
	}
35971 aman 460
</style>
461
 
462
<section class="wrapper">
463
	<div class="row">
464
		<div class="col-lg-12">
465
			<h3 class="page-header" style="color:#2c3e50;">
466
				<i class="icon_document_alt"></i> Partner Onboarding Timeline
467
			</h3>
468
			<ol class="breadcrumb">
469
				<li><i class="fa fa-home"></i><a href="${rc.contextPath}/dashboard">Home</a></li>
470
				<li><i class="icon_document_alt"></i>Onboarding Timeline</li>
471
			</ol>
472
		</div>
473
	</div>
474
 
36242 aman 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
 
35971 aman 513
	<div class="col-lg-12" style="overflow-x:auto;">
514
		<table class="timeline-table" id="storeTimeline">
515
			<thead>
516
			<tr>
517
				<th rowspan="2">ID</th>
518
				<th rowspan="2">Partner</th>
519
				<th rowspan="2">Code</th>
520
				<th rowspan="2">Status</th>
36242 aman 521
				<th rowspan="2" style="background:#c0392b;">Delay</th>
35971 aman 522
				<th colspan="4" style="background:#8e44ad;"><span class="section-header">LOI Process</span></th>
523
				<th colspan="4" style="background:#2471a3;"><span class="section-header">Onboarding</span></th>
524
				<th colspan="8" style="background:#1e8449;"><span class="section-header">Store Setup</span></th>
525
				<th colspan="3" style="background:#d35400;"><span class="section-header">Launch</span></th>
526
			</tr>
527
			<tr>
528
				<!-- LOI Process -->
529
				<th style="background:#9b59b6;">LOI Form</th>
530
				<th style="background:#9b59b6;">BM Approval</th>
531
				<th style="background:#9b59b6;">Doc Approval</th>
532
				<th style="background:#9b59b6;">Payment Approval</th>
533
				<!-- Onboarding -->
534
				<th style="background:#2980b9;">Onboarding</th>
535
				<th style="background:#2980b9;">Verification</th>
536
				<th style="background:#2980b9;">Store Code</th>
537
				<th style="background:#2980b9;">Welcome Call</th>
538
				<!-- Store Setup -->
539
				<th style="background:#27ae60;">Recce</th>
540
				<th style="background:#27ae60;">WOD</th>
541
				<th style="background:#27ae60;">Fin Code</th>
542
				<th style="background:#27ae60;">WOD-Fin Map</th>
543
				<th style="background:#27ae60;">Branding</th>
544
				<th style="background:#27ae60;">Full Stock</th>
545
				<th style="background:#27ae60;">PO Creation</th>
546
				<th style="background:#27ae60;">PO Approval</th>
547
				<!-- Launch -->
548
				<th style="background:#e67e22;">Billing</th>
549
				<th style="background:#e67e22;">Inauguration</th>
550
				<th style="background:#e67e22;">Training</th>
551
			</tr>
552
			</thead>
553
			<tbody>
554
				#foreach($st in $storeTimelines)
36242 aman 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="">
564
					#end
35971 aman 565
					<td><strong>$st.getOnboardingId()</strong></td>
566
					<td style="text-align:left;max-width:120px;overflow:hidden;text-overflow:ellipsis;"
567
						title="$st.getOutletName() ($st.getCity())">$st.getOutletName() ($st.getCity())
568
					</td>
569
					#if($st.getCode())
570
						<td>$st.getCode()</td>
571
					#else
572
						<td style="color:#ccc;">-</td>
573
					#end
574
					<td>
575
						#set($statusLower = $st.getStatus().toLowerCase())
576
						#if($statusLower == "open")
577
							<span class="status-badge status-open">$st.getStatus()</span>
578
						#elseif($statusLower == "pending")
579
							<span class="status-badge status-pending">$st.getStatus()</span>
580
						#elseif($statusLower == "completed")
581
							<span class="status-badge status-completed">$st.getStatus()</span>
582
						#elseif($statusLower == "hold")
583
							<span class="status-badge status-hold">$st.getStatus()</span>
584
						#else
585
							<span class="status-badge status-rejected">$st.getStatus()</span>
586
						#end
587
					</td>
36242 aman 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
35971 aman 614
					#if($st.getObtm() && $st.getObtm().size() > 0)
615
						#foreach($obtm in $st.getObtm())
616
							#if($obtm.getStatus().toString() == "COMPLETED")
36242 aman 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
630
									<td><span class="tl-cell"><span
631
											class="tl-dot completed"></span>#if($obtm.getCompletedTimestamp())<span
632
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
633
									</td>
634
								#end
35971 aman 635
							#elseif($obtm.getStatus().toString() == "DELAY")
36242 aman 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)
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
654
											class="tl-dot delay"></span>#if($obtm.getCompletedTimestamp())<span
655
											class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
656
									</td>
657
								#end
35971 aman 658
							#elseif($obtm.getStatus().toString() == "WIP")
659
								<td style="background:#fffde7;"><span class="tl-cell"><span
660
										class="tl-dot wip"></span>#if($obtm.getCompletedTimestamp())<span
661
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
662
								</td>
663
							#elseif($obtm.getStatus().toString() == "NOT_STARTED")
664
								<td><span class="tl-cell"><span
665
										class="tl-dot not-started"></span>#if($obtm.getCompletedTimestamp())<span
666
										class="tl-date">$obtm.getCompletedTimestamp().format($dateFormatter)</span>#end</span>
667
								</td>
668
							#else
669
								<td style="color:#ccc;">-</td>
670
							#end
671
						#end
672
					#else
673
						#foreach($i in [1..19])
674
							<td style="color:#ccc;">-</td>
675
						#end
676
					#end
677
				</tr>
678
				#end
679
			</tbody>
680
		</table>
681
	</div>
682
 
683
	<div id="timeline-detail-container" style="display:none; margin-top:15px;">
684
		<div class="panel panel-default">
685
			<div class="panel-heading">
686
				<button type="button" class="close" id="closeDetailPanel">&times;</button>
687
				<h4 class="panel-title">
688
					<span id="detail-partner-name"></span>
689
					<small id="detail-partner-code" class="text-muted" style="margin-left:10px;"></small>
690
				</h4>
691
			</div>
692
			<div class="panel-body" id="timeline-detail-body">
693
				<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>
694
			</div>
695
		</div>
696
	</div>
29061 tejbeer 697
</section>
698
 
699
<script type="text/javascript">
35971 aman 700
	$(document).ready(function () {
36242 aman 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
 
35971 aman 724
		var dtable = $('#storeTimeline').DataTable({
725
			scrollX: true,
36024 aman 726
			scrollY: '75vh',
727
			scrollCollapse: true,
35971 aman 728
			orderCellsTop: true,
729
			order: [[0, "desc"]],
36024 aman 730
			paging: false,
35971 aman 731
			fixedColumns: {
36242 aman 732
				leftColumns: 5
35971 aman 733
			},
36024 aman 734
			dom: '<"top-controls"f>rt<"bottom"i>',
35971 aman 735
			language: {
36024 aman 736
				search: "Search Partners:"
35971 aman 737
			}
738
		});
29061 tejbeer 739
 
36242 aman 740
		// Legend is now rendered as static HTML above the table (not inside DataTables controls).
29061 tejbeer 741
 
36242 aman 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 () {
746
			var team = $(this).attr('data-delay-team');
747
			if (team) teamSet[team] = true;
748
		});
749
		var teamSelect = $('#tlTeamFilter');
750
		Object.keys(teamSet).sort().forEach(function (team) {
751
			teamSelect.append($('<option></option>').val(team).text(team));
752
		});
753
 
754
		// applyTab — single source of truth for switching tabs.
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');
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
 
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();
777
			dtable.draw();
778
		});
779
 
780
		// Honor an optional defaultTab model attribute set by the controller.
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
		}
787
 
35971 aman 788
		// Row click -> load detail panel
789
		$('#storeTimeline tbody').on('click', 'tr', function () {
790
			var $row = $(this);
791
			var onboardingId = $.trim($row.find('td:first').text());
792
			if (!onboardingId || isNaN(onboardingId)) return;
793
 
794
			// Highlight selected row (both main table and fixed-column clone)
795
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
796
			var rowIndex = dtable.row($row).index();
797
			$('#storeTimeline tbody tr').eq(rowIndex).addClass('row-selected');
798
			$('.DTFC_LeftBodyWrapper table tbody tr').eq(rowIndex).addClass('row-selected');
799
 
800
			var partnerName = $.trim($row.find('td:eq(1)').text());
801
			var partnerCode = $.trim($row.find('td:eq(2)').text());
802
 
803
			$('#detail-partner-name').text(partnerName);
804
			$('#detail-partner-code').text(partnerCode !== '-' ? partnerCode : '');
805
			$('#timeline-detail-body').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
806
			$('#storeTimeline_wrapper').hide();
807
			$('#timeline-detail-container').show();
808
 
809
			$.ajax({
810
				url: '${rc.contextPath}/partnerTimelineDetail',
811
				data: {onboardingId: onboardingId},
812
				success: function (html) {
813
					$('#timeline-detail-body').html(html);
814
				},
815
				error: function () {
816
					$('#timeline-detail-body').html('<div class="alert alert-danger">Failed to load details.</div>');
817
				}
818
			});
819
 
820
			$('html, body').animate({scrollTop: $('#timeline-detail-container').offset().top - 60}, 300);
821
		});
822
 
823
		// Close detail panel
824
		$(document).on('click', '#closeDetailPanel', function () {
825
			$('#timeline-detail-container').hide();
826
			$('#storeTimeline_wrapper').show();
827
			dtable.columns.adjust();
828
			$('#storeTimeline tbody tr, .DTFC_LeftBodyWrapper table tbody tr').removeClass('row-selected');
829
		});
36242 aman 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
		});
35971 aman 928
	});
36242 aman 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>