Subversion Repositories SmartDukaan

Rev

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