Subversion Repositories SmartDukaan

Rev

Rev 37576 | Rev 37583 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
37444 ranu 1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
  <title>RBM DRR Dashboard</title>
7
  <style>
8
    :root {
9
      --bg: #f4f6fb;
10
      --card: #ffffff;
11
      --ink: #141a2b;
12
      --muted: #5d6a83;
13
      --soft: #97a1b5;
14
      --line: #e6e9f1;
15
      --line2: #eef1f6;
16
      --brand: #3b5bdb;
17
      --brand2: #5c7cfa;
18
      --teal: #0ca678;
19
      --cyan: #1098ad;
20
      --amber: #f59f00;
21
      --orange: #e8590c;
22
      --red: #e03131;
23
      --violet: #7048e8;
24
      --pink: #d6336c;
25
      --good: #2f9e44;
26
      --warn: #e8590c;
27
      --bad: #e03131;
28
      --hid: #7048e8;
29
      --fast: #3b5bdb;
30
      --run: #0ca678;
31
      --slow: #e8590c;
32
      --other: #868e96;
33
      --hid-bg: #f2edff;
34
      --fast-bg: #eaf0ff;
35
      --run-bg: #e5f7ee;
36
      --slow-bg: #ffefe0;
37
      --other-bg: #eef0f4;
38
    }
39
 
40
    * {
41
      box-sizing: border-box
42
    }
43
 
44
    html, body {
45
      margin: 0;
46
      background: var(--bg);
47
      color: var(--ink);
48
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
49
      -webkit-font-smoothing: antialiased;
50
      font-size: 14px
51
    }
52
 
53
    .wrap {
37523 ranu 54
      max-width: none;
37444 ranu 55
      margin: 0 auto;
37523 ranu 56
      padding: 22px 20px 100px
37444 ranu 57
    }
58
 
59
    a {
60
      color: var(--brand);
61
      text-decoration: none
62
    }
63
 
64
    a:hover {
65
      text-decoration: underline
66
    }
67
 
68
    /* header */
69
    header.top {
70
      display: flex;
71
      align-items: flex-end;
72
      justify-content: space-between;
73
      gap: 18px;
74
      flex-wrap: wrap;
75
      margin-bottom: 20px
76
    }
77
 
78
    .brand .crumb {
79
      font-size: 12px;
80
      color: var(--soft);
81
      font-weight: 600
82
    }
83
 
84
    .brand h1 {
85
      margin: 4px 0 0;
86
      font-size: 26px;
87
      font-weight: 770;
88
      letter-spacing: -.015em;
89
      display: flex;
90
      align-items: center;
91
      gap: 12px
92
    }
93
 
94
    .brand h1 .live {
95
      font-size: 11px;
96
      font-weight: 700;
97
      color: var(--teal);
98
      background: #e6fcf5;
99
      border: 1px solid #c3fae8;
100
      border-radius: 999px;
101
      padding: 3px 10px
102
    }
103
 
104
    .brand h1 .live i {
105
      display: inline-block;
106
      width: 7px;
107
      height: 7px;
108
      border-radius: 50%;
109
      background: var(--teal);
110
      margin-right: 5px;
111
      animation: pulse 1.6s infinite;
112
      vertical-align: middle
113
    }
114
 
115
    @keyframes pulse {
116
      0%, 100% {
117
        opacity: 1
118
      }
119
      50% {
120
        opacity: .25
121
      }
122
    }
123
 
124
    .hd-right {
125
      display: flex;
126
      align-items: center;
127
      gap: 10px;
128
      flex-wrap: wrap
129
    }
130
 
131
    .ctl {
132
      background: var(--card);
133
      border: 1px solid var(--line);
134
      border-radius: 10px;
135
      padding: 9px 13px;
136
      font-size: 13px;
137
      color: var(--ink);
138
      font-weight: 600;
139
      box-shadow: 0 1px 2px rgba(20, 30, 60, .04)
140
    }
141
 
142
    input.ctl {
143
      appearance: none;
144
      cursor: pointer;
145
      height: 38px;
146
      min-width: 240px
147
    }
148
 
149
    .btn-r {
150
      background: var(--brand);
151
      border: 0;
152
      color: #fff;
153
      font-weight: 670;
154
      padding: 9px 18px;
155
      border-radius: 10px;
156
      cursor: pointer;
157
      font-size: 13px;
158
      height: 38px;
159
      box-shadow: 0 1px 2px rgba(59, 91, 219, .35);
160
      transition: transform .1s, box-shadow .1s
161
    }
162
 
163
    .btn-r:hover {
164
      background: var(--brand2);
165
      box-shadow: 0 3px 10px rgba(59, 91, 219, .35)
166
    }
167
 
168
    .btn-r:active {
169
      transform: translateY(1px)
170
    }
171
 
172
    /* KPI cards */
173
    .kpis {
174
      display: grid;
175
      grid-template-columns:repeat(4, 1fr);
176
      gap: 14px;
177
      margin-bottom: 22px
178
    }
179
 
180
    .kpi {
181
      position: relative;
182
      background: var(--card);
183
      border: 1px solid var(--line);
184
      border-radius: 14px;
185
      padding: 16px 18px;
186
      box-shadow: 0 1px 2px rgba(20, 30, 60, .05);
187
      display: flex;
188
      flex-direction: column;
189
      min-height: 118px;
190
      overflow: hidden
191
    }
192
 
193
    .kpi::before {
194
      content: "";
195
      position: absolute;
196
      top: 0;
197
      left: 0;
198
      height: 3px;
199
      width: 100%;
200
      border-top-left-radius: 14px;
201
      border-top-right-radius: 14px
202
    }
203
 
204
    .kpi.a::before {
205
      background: linear-gradient(90deg, var(--brand), var(--brand2))
206
    }
207
 
208
    .kpi.b::before {
209
      background: linear-gradient(90deg, var(--teal), #20c997)
210
    }
211
 
212
    .kpi.c::before {
213
      background: linear-gradient(90deg, var(--amber), #ffc078)
214
    }
215
 
216
    .kpi.d::before {
217
      background: linear-gradient(90deg, var(--violet), #9775fa)
218
    }
219
 
220
    .kpi .top {
221
      display: flex;
222
      align-items: center;
223
      gap: 10px
224
    }
225
 
226
    .kpi .ic {
227
      width: 34px;
228
      height: 34px;
229
      border-radius: 10px;
230
      display: flex;
231
      align-items: center;
232
      justify-content: center;
233
      flex: none;
234
      color: #fff;
235
      box-shadow: 0 2px 6px rgba(0, 0, 0, .08)
236
    }
237
 
238
    .kpi.a .ic {
239
      background: linear-gradient(135deg, var(--brand), var(--brand2))
240
    }
241
 
242
    .kpi.b .ic {
243
      background: linear-gradient(135deg, var(--teal), #20c997)
244
    }
245
 
246
    .kpi.c .ic {
247
      background: linear-gradient(135deg, var(--amber), #ffc078);
248
      color: #3a2500
249
    }
250
 
251
    .kpi.d .ic {
252
      background: linear-gradient(135deg, var(--violet), #9775fa)
253
    }
254
 
255
    .kpi .ic svg {
256
      width: 18px;
257
      height: 18px
258
    }
259
 
260
    .kpi .label {
261
      font-size: 12px;
262
      color: var(--muted);
263
      font-weight: 650;
264
      line-height: 1.2;
265
      letter-spacing: .005em
266
    }
267
 
268
    .kpi .val {
269
      font-size: 28px;
270
      font-weight: 780;
271
      margin-top: auto;
272
      padding-top: 10px;
273
      letter-spacing: -.02em;
274
      line-height: 1;
275
      font-variant-numeric: tabular-nums
276
    }
277
 
278
    .kpi .val .u {
279
      font-size: 13px;
280
      color: var(--soft);
281
      font-weight: 600;
282
      margin-left: 3px
283
    }
284
 
285
    .kpi .foot {
286
      font-size: 11.5px;
287
      color: var(--soft);
288
      margin-top: 8px;
289
      font-weight: 500
290
    }
291
 
292
    .kpi .val .pct {
293
      margin-left: 0;
294
      font-size: 20px;
295
      padding: 4px 12px
296
    }
297
 
298
    .kpi.clickable {
299
      cursor: pointer;
300
      transition: transform .1s, box-shadow .12s, border-color .12s
301
    }
302
 
303
    .kpi.clickable:hover {
304
      transform: translateY(-2px);
305
      box-shadow: 0 6px 18px rgba(20, 30, 60, .08);
306
      border-color: #c8d2f4
307
    }
308
 
309
    .kpi.clickable .hint {
310
      position: absolute;
311
      bottom: 8px;
312
      right: 12px;
313
      color: var(--soft);
314
      font-size: 11px;
315
      font-weight: 600;
316
      opacity: .7
317
    }
318
 
319
    .kpi.clickable:hover .hint {
320
      opacity: 1;
321
      color: var(--brand)
322
    }
323
 
324
    /* Cards & section labels */
325
    .card {
326
      background: var(--card);
327
      border: 1px solid var(--line);
328
      border-radius: 15px;
329
      padding: 18px 20px;
330
      margin-bottom: 16px;
331
      box-shadow: 0 1px 2px rgba(20, 30, 60, .04)
332
    }
333
 
334
    .card .card-hd {
335
      display: flex;
336
      align-items: baseline;
337
      justify-content: space-between;
338
      gap: 14px;
339
      margin-bottom: 14px
340
    }
341
 
342
    .card h2 {
343
      margin: 0;
344
      font-size: 16px;
345
      font-weight: 730;
346
      letter-spacing: -.01em
347
    }
348
 
349
    .card .desc {
350
      color: var(--muted);
351
      font-size: 12.5px;
352
      margin: 2px 0 0
353
    }
354
 
355
    .card .badge-period {
356
      background: #eef2ff;
357
      border: 1px solid #d7defb;
358
      color: var(--brand);
359
      border-radius: 999px;
360
      padding: 3px 12px;
361
      font-size: 12px;
362
      font-weight: 650;
363
      letter-spacing: .01em
364
    }
365
 
366
    .sectionlabel {
367
      font-size: 11px;
368
      letter-spacing: .14em;
369
      text-transform: uppercase;
370
      color: var(--soft);
371
      font-weight: 700;
372
      margin: 20px 2px 10px
373
    }
374
 
375
    /* Legend under section label */
376
    .legend {
377
      display: flex;
378
      gap: 14px;
379
      flex-wrap: wrap;
380
      margin: 0 2px 10px;
381
      align-items: center;
382
      color: var(--soft);
383
      font-size: 11.5px;
384
      font-weight: 600
385
    }
386
 
387
    .legend .lg {
388
      display: inline-flex;
389
      align-items: center;
390
      gap: 5px
391
    }
392
 
393
    .legend .lg i {
394
      width: 8px;
395
      height: 8px;
396
      border-radius: 50%;
397
      display: inline-block
398
    }
399
 
400
    .legend .lg.hid i {
401
      background: var(--hid)
402
    }
403
 
404
    .legend .lg.fast i {
405
      background: var(--fast)
406
    }
407
 
408
    .legend .lg.run i {
409
      background: var(--run)
410
    }
411
 
412
    .legend .lg.slow i {
413
      background: var(--slow)
414
    }
415
 
416
    .legend .lg.other i {
417
      background: var(--other)
418
    }
419
 
420
    .legend .lg .lgt {
421
      color: var(--muted)
422
    }
423
 
424
    /* Table */
425
    table {
426
      width: 100%;
427
      border-collapse: separate;
428
      border-spacing: 0;
429
      font-size: 13px
430
    }
431
 
432
    th, td {
433
      text-align: left;
434
      padding: 11px 10px;
435
      border-bottom: 1px solid var(--line2)
436
    }
437
 
438
    thead th {
439
      font-size: 10.5px;
440
      text-transform: uppercase;
441
      letter-spacing: .06em;
442
      color: var(--soft);
443
      font-weight: 700;
444
      white-space: nowrap;
445
      user-select: none;
446
      position: sticky;
447
      top: 0;
448
      background: #fff;
449
      z-index: 1;
450
      border-bottom: 1px solid var(--line)
451
    }
452
 
453
    th.num, td.num {
454
      text-align: right
455
    }
456
 
457
    th.g-hid {
458
      color: var(--hid);
459
      border-top: 3px solid var(--hid)
460
    }
461
 
462
    th.g-fast {
463
      color: var(--fast);
464
      border-top: 3px solid var(--fast)
465
    }
466
 
467
    th.g-run {
468
      color: var(--run);
469
      border-top: 3px solid var(--run)
470
    }
471
 
472
    th.g-slow {
473
      color: var(--slow);
474
      border-top: 3px solid var(--slow)
475
    }
476
 
477
    th.g-other {
478
      color: var(--other);
479
      border-top: 3px solid var(--other)
480
    }
481
 
482
    th.g-total {
483
      color: var(--brand);
484
      border-top: 3px solid var(--brand)
485
    }
486
 
487
    tbody tr:hover {
488
      background: #f6f8fd
489
    }
490
 
491
    tbody tr.clickable {
492
      cursor: pointer;
493
      transition: background .1s
494
    }
495
 
496
    tbody tr.clickable:hover {
497
      background: #eef2ff
498
    }
499
 
500
    tr.totalrow {
501
      background: #fafbfe;
502
      border-top: 2px solid var(--line)
503
    }
504
 
505
    tr.totalrow td {
506
      font-weight: 770;
507
      color: var(--ink)
508
    }
509
 
510
    tr.totalrow .cell-val {
511
      font-size: 14px
512
    }
513
 
514
    .rbm-cell {
515
      display: flex;
516
      flex-direction: column;
517
      gap: 2px
518
    }
519
 
520
    .rbm-cell .rn {
521
      font-weight: 700;
522
      color: var(--ink);
523
      font-size: 13.5px
524
    }
525
 
526
    .rbm-cell .wh {
527
      font-size: 11.5px;
528
      color: var(--muted);
529
      font-weight: 600
530
    }
531
 
532
    .cell-val {
533
      font-variant-numeric: tabular-nums;
534
      font-weight: 650;
535
      color: var(--ink);
536
      white-space: nowrap
537
    }
538
 
539
    .cell-val small {
540
      color: var(--soft);
541
      font-weight: 500;
542
      margin-left: 3px
543
    }
544
 
545
    .pct {
546
      display: inline-flex;
547
      align-items: center;
548
      justify-content: center;
549
      min-width: 56px;
550
      padding: 3px 9px;
551
      border-radius: 7px;
552
      font-size: 11.5px;
553
      font-weight: 700;
554
      text-align: center;
555
      font-variant-numeric: tabular-nums;
556
      line-height: 1.35
557
    }
558
 
559
    .pct.good {
560
      background: #e5f7ee;
561
      color: #177a3a
562
    }
563
 
564
    .pct.warn {
565
      background: #fff0e0;
566
      color: #a04600
567
    }
568
 
569
    .pct.bad {
570
      background: #ffeaea;
571
      color: #b8272b
572
    }
573
 
574
    .pct.na {
575
      background: transparent;
576
      color: var(--soft);
577
      font-weight: 600;
578
      min-width: auto;
579
      padding: 0
580
    }
581
 
582
    .bar {
583
      height: 5px;
584
      border-radius: 3px;
585
      background: #eef1f6;
586
      overflow: hidden;
587
      margin-top: 6px
588
    }
589
 
590
    .bar > i {
591
      display: block;
592
      height: 100%;
593
      border-radius: 3px;
594
      transition: width .3s ease
595
    }
596
 
597
    .bar > i.good {
598
      background: linear-gradient(90deg, #51cf66, #2f9e44)
599
    }
600
 
601
    .bar > i.warn {
602
      background: linear-gradient(90deg, #ffa94d, #e8590c)
603
    }
604
 
605
    .bar > i.bad {
606
      background: linear-gradient(90deg, #ff8787, #e03131)
607
    }
608
 
609
    .chev {
610
      color: #c5cce0;
611
      font-size: 16px;
612
      padding-left: 6px;
613
      transition: transform .12s, color .12s
614
    }
615
 
616
    tr.clickable:hover .chev {
617
      color: var(--brand);
618
      transform: translateX(2px)
619
    }
620
 
621
    .loading {
622
      padding: 70px;
623
      text-align: center;
624
      color: var(--muted)
625
    }
626
 
627
    .spinner {
628
      width: 32px;
629
      height: 32px;
630
      border: 3px solid #e3e8f5;
631
      border-top-color: var(--brand);
632
      border-radius: 50%;
633
      animation: spin 1s linear infinite;
634
      margin: 0 auto 12px
635
    }
636
 
637
    @keyframes spin {
638
      to {
639
        transform: rotate(360deg)
640
      }
641
    }
642
 
643
    .empty {
644
      padding: 56px 20px;
645
      text-align: center;
646
      color: var(--soft);
647
      font-size: 13.5px
648
    }
649
 
650
    .empty .ei {
651
      font-size: 36px;
652
      margin-bottom: 8px;
653
      opacity: .5
654
    }
655
 
656
    .foot-note {
657
      color: var(--soft);
658
      font-size: 11.5px;
659
      margin-top: 14px;
660
      padding: 10px 12px;
661
      background: #fafbfe;
662
      border: 1px dashed var(--line);
663
      border-radius: 10px;
664
      line-height: 1.5
665
    }
666
 
667
    .foot-note code {
668
      background: #eef1f6;
669
      color: var(--muted);
670
      padding: 1px 5px;
671
      border-radius: 4px;
672
      font-size: 11px
673
    }
674
 
675
    /* drill-down panel */
676
    .rdrr-back {
677
      position: fixed;
678
      inset: 0;
679
      background: rgba(15, 22, 40, .5);
680
      backdrop-filter: blur(2px);
681
      z-index: 1200;
682
      display: none
683
    }
684
 
685
    .rdrr-back.show {
686
      display: block
687
    }
688
 
689
    .rdrr-panel {
690
      position: absolute;
691
      top: 0;
692
      right: 0;
693
      height: 100%;
694
      width: min(760px, 96vw);
695
      background: #fff;
696
      box-shadow: -12px 0 40px rgba(20, 30, 60, .22);
697
      display: flex;
698
      flex-direction: column;
699
      padding: 24px 26px 20px;
700
      transform: translateX(100%);
701
      transition: transform .22s ease;
702
      overflow-y: auto
703
    }
704
 
705
    .rdrr-back.show .rdrr-panel {
706
      transform: translateX(0)
707
    }
708
 
709
    .rdrr-panel .x {
710
      position: absolute;
711
      top: 16px;
712
      right: 18px;
713
      cursor: pointer;
714
      color: var(--soft);
715
      font-size: 24px;
716
      line-height: 1;
717
      border: 0;
718
      background: none;
719
      width: 32px;
720
      height: 32px;
721
      border-radius: 8px;
722
      transition: background .1s, color .1s
723
    }
724
 
725
    .rdrr-panel .x:hover {
726
      background: #f1f3f9;
727
      color: var(--ink)
728
    }
729
 
730
    .rdrr-panel h3 {
731
      margin: 0 0 3px;
732
      font-size: 19px;
733
      font-weight: 750;
734
      letter-spacing: -.01em;
735
      padding-right: 40px
736
    }
737
 
738
    .rdrr-panel > .desc {
739
      margin: 0 0 18px;
740
      font-size: 12.5px;
741
      color: var(--muted);
742
      font-weight: 600
743
    }
744
 
745
    .rdrr-total {
746
      display: flex;
747
      gap: 14px;
748
      padding: 14px 16px;
749
      background: linear-gradient(135deg, #f8f9fc, #eef2ff);
750
      border: 1px solid #dbe2f6;
751
      border-radius: 12px;
752
      margin-bottom: 18px
753
    }
754
 
755
    .rdrr-total > div {
756
      flex: 1;
757
      text-align: center;
758
      border-right: 1px solid #dbe2f6;
759
      padding: 0 4px
760
    }
761
 
762
    .rdrr-total > div:last-child {
763
      border-right: 0
764
    }
765
 
766
    .rdrr-total .k {
767
      font-size: 10.5px;
768
      color: var(--muted);
769
      text-transform: uppercase;
770
      letter-spacing: .06em;
771
      font-weight: 700
772
    }
773
 
774
    .rdrr-total .v {
775
      font-size: 20px;
776
      font-weight: 770;
777
      color: var(--ink);
778
      font-variant-numeric: tabular-nums;
779
      margin-top: 5px;
780
      letter-spacing: -.01em
781
    }
782
 
783
    .rdrr-total .v .pct {
784
      font-size: 16px;
785
      padding: 3px 10px;
786
      margin-top: 0
787
    }
788
 
789
    .bbars {
790
      display: flex;
791
      flex-direction: column;
792
      gap: 10px;
793
      margin-bottom: 20px
794
    }
795
 
796
    .bbar {
797
      display: grid;
798
      grid-template-columns:82px 1fr 145px 60px;
799
      gap: 14px;
800
      align-items: center;
801
      padding: 12px 14px;
802
      background: #fff;
803
      border: 1px solid var(--line);
804
      border-radius: 11px;
805
      transition: box-shadow .12s
806
    }
807
 
808
    .bbar:hover {
809
      box-shadow: 0 3px 10px rgba(20, 30, 60, .06)
810
    }
811
 
812
    .bbar .bname {
813
      font-size: 11.5px;
814
      font-weight: 750;
815
      color: var(--ink);
816
      letter-spacing: .04em;
817
      text-transform: uppercase;
818
      display: flex;
819
      align-items: center;
820
      gap: 6px
821
    }
822
 
823
    .bbar .bname i {
824
      display: inline-block;
825
      width: 8px;
826
      height: 8px;
827
      border-radius: 50%
828
    }
829
 
830
    .bbar.hid .bname {
831
      color: var(--hid)
832
    }
833
 
834
    .bbar.fast .bname {
835
      color: var(--fast)
836
    }
837
 
838
    .bbar.run .bname {
839
      color: var(--run)
840
    }
841
 
842
    .bbar.slow .bname {
843
      color: var(--slow)
844
    }
845
 
846
    .bbar.other .bname {
847
      color: var(--other)
848
    }
849
 
850
    .bbar.hid .bname i {
851
      background: var(--hid)
852
    }
853
 
854
    .bbar.fast .bname i {
855
      background: var(--fast)
856
    }
857
 
858
    .bbar.run .bname i {
859
      background: var(--run)
860
    }
861
 
862
    .bbar.slow .bname i {
863
      background: var(--slow)
864
    }
865
 
866
    .bbar.other .bname i {
867
      background: var(--other)
868
    }
869
 
870
    .bbar .btrack {
871
      height: 14px;
872
      border-radius: 8px;
873
      overflow: hidden;
874
      position: relative
875
    }
876
 
877
    .bbar.hid .btrack {
878
      background: var(--hid-bg)
879
    }
880
 
881
    .bbar.fast .btrack {
882
      background: var(--fast-bg)
883
    }
884
 
885
    .bbar.run .btrack {
886
      background: var(--run-bg)
887
    }
888
 
889
    .bbar.slow .btrack {
890
      background: var(--slow-bg)
891
    }
892
 
893
    .bbar.other .btrack {
894
      background: var(--other-bg)
895
    }
896
 
897
    .bbar .btrack > i {
898
      display: block;
899
      height: 100%;
900
      border-radius: 8px;
901
      transition: width .3s ease;
902
      position: relative
903
    }
904
 
905
    .bbar.hid .btrack > i {
906
      background: linear-gradient(90deg, #9775fa, var(--hid))
907
    }
908
 
909
    .bbar.fast .btrack > i {
910
      background: linear-gradient(90deg, var(--brand2), var(--fast))
911
    }
912
 
913
    .bbar.run .btrack > i {
914
      background: linear-gradient(90deg, #20c997, var(--run))
915
    }
916
 
917
    .bbar.slow .btrack > i {
918
      background: linear-gradient(90deg, #ffa94d, var(--slow))
919
    }
920
 
921
    .bbar.other .btrack > i {
922
      background: linear-gradient(90deg, #adb5bd, var(--other))
923
    }
924
 
925
    .bbar .btgt {
926
      font-size: 12px;
927
      color: var(--muted);
928
      font-variant-numeric: tabular-nums;
929
      font-weight: 600;
930
      text-align: right
931
    }
932
 
933
    .bbar .btgt b {
934
      color: var(--ink);
935
      font-weight: 700
936
    }
937
 
938
    .bbar .bpct {
939
      font-size: 13px;
940
      font-weight: 750;
941
      text-align: right;
942
      font-variant-numeric: tabular-nums
943
    }
944
 
945
    .bbar .bpct.good {
946
      color: var(--good)
947
    }
948
 
949
    .bbar .bpct.warn {
950
      color: var(--warn)
951
    }
952
 
953
    .bbar .bpct.bad {
954
      color: var(--bad)
955
    }
956
 
957
    .bbar .bpct.na {
958
      color: var(--soft);
959
      font-weight: 600
960
    }
961
 
37487 ranu 962
    /* warehouse flow (buying vs selling) in drill-down */
963
    .flow-tbl {
964
      width: 100%;
965
      border-collapse: separate;
966
      border-spacing: 0;
967
      font-size: 12.5px;
968
      margin-bottom: 20px;
969
      background: #fff;
970
      border: 1px solid var(--line);
971
      border-radius: 11px;
972
      overflow: hidden
973
    }
974
 
975
    .flow-tbl th, .flow-tbl td {
976
      padding: 10px 12px;
977
      border-bottom: 1px solid var(--line2);
978
      text-align: right;
979
      font-variant-numeric: tabular-nums
980
    }
981
 
982
    .flow-tbl th {
983
      font-size: 10.5px;
984
      text-transform: uppercase;
985
      letter-spacing: .05em;
986
      color: var(--soft);
987
      font-weight: 700;
988
      background: #fafbfe;
989
      text-align: left;
990
      padding-left: 14px
991
    }
992
 
993
    .flow-tbl th.num, .flow-tbl td.num {
994
      text-align: right
995
    }
996
 
997
    .flow-tbl thead th:first-child {
998
      text-align: left
999
    }
1000
 
1001
    .flow-tbl tbody tr td:first-child {
1002
      text-align: left;
1003
      padding-left: 14px;
1004
      font-weight: 700;
1005
      letter-spacing: .03em;
1006
      text-transform: uppercase;
1007
      font-size: 11px
1008
    }
1009
 
1010
    .flow-tbl tbody tr td:first-child i {
1011
      display: inline-block;
1012
      width: 7px;
1013
      height: 7px;
1014
      border-radius: 50%;
1015
      margin-right: 6px;
1016
      vertical-align: middle
1017
    }
1018
 
1019
    .flow-tbl tbody tr:last-child td {
1020
      border-bottom: 0
1021
    }
1022
 
1023
    .flow-tbl tbody tr.hid td:first-child {
1024
      color: var(--hid)
1025
    }
1026
 
1027
    .flow-tbl tbody tr.hid td:first-child i {
1028
      background: var(--hid)
1029
    }
1030
 
1031
    .flow-tbl tbody tr.fastmoving td:first-child {
1032
      color: var(--fast)
1033
    }
1034
 
1035
    .flow-tbl tbody tr.fastmoving td:first-child i {
1036
      background: var(--fast)
1037
    }
1038
 
1039
    .flow-tbl tbody tr.running td:first-child {
1040
      color: var(--run)
1041
    }
1042
 
1043
    .flow-tbl tbody tr.running td:first-child i {
1044
      background: var(--run)
1045
    }
1046
 
1047
    .flow-tbl tbody tr.slowmoving td:first-child {
1048
      color: var(--slow)
1049
    }
1050
 
1051
    .flow-tbl tbody tr.slowmoving td:first-child i {
1052
      background: var(--slow)
1053
    }
1054
 
1055
    .flow-tbl tbody tr.other td:first-child {
1056
      color: var(--other)
1057
    }
1058
 
1059
    .flow-tbl tbody tr.other td:first-child i {
1060
      background: var(--other)
1061
    }
1062
 
1063
    .flow-tbl .cov-cell {
1064
      display: inline-flex;
1065
      align-items: center;
1066
      gap: 6px;
1067
      justify-content: flex-end
1068
    }
1069
 
1070
    .flow-tbl .cov-cell .cov-bar {
1071
      width: 60px;
1072
      height: 6px;
1073
      border-radius: 3px;
1074
      background: #eef1f6;
1075
      overflow: hidden;
1076
      flex: none
1077
    }
1078
 
1079
    .flow-tbl .cov-cell .cov-bar > i {
1080
      display: block;
1081
      height: 100%;
1082
      border-radius: 3px
1083
    }
1084
 
1085
    .flow-tbl .cov-cell.good .cov-bar > i {
1086
      background: var(--good)
1087
    }
1088
 
1089
    .flow-tbl .cov-cell.warn .cov-bar > i {
1090
      background: var(--warn)
1091
    }
1092
 
1093
    .flow-tbl .cov-cell.bad .cov-bar > i {
1094
      background: var(--bad)
1095
    }
1096
 
1097
    .flow-tbl .cov-cell.good .cov-pct {
1098
      color: var(--good);
1099
      font-weight: 700
1100
    }
1101
 
1102
    .flow-tbl .cov-cell.warn .cov-pct {
1103
      color: var(--warn);
1104
      font-weight: 700
1105
    }
1106
 
1107
    .flow-tbl .cov-cell.bad .cov-pct {
1108
      color: var(--bad);
1109
      font-weight: 700
1110
    }
1111
 
1112
    .flow-tbl .cov-cell.na {
1113
      color: var(--soft);
1114
      font-weight: 500
1115
    }
1116
 
1117
    .flow-tbl tr.tot td {
1118
      font-weight: 770;
1119
      background: #fafbfe;
1120
      color: var(--ink);
1121
      border-top: 2px solid var(--line)
1122
    }
1123
 
37444 ranu 1124
    /* brand mix bars in drill-down */
1125
    .brands {
1126
      display: flex;
1127
      flex-direction: column;
1128
      gap: 8px;
1129
      margin-bottom: 20px
1130
    }
1131
 
1132
    .brand-card {
1133
      background: #fff;
1134
      border: 1px solid var(--line);
1135
      border-radius: 11px;
1136
      overflow: hidden;
1137
      transition: box-shadow .12s
1138
    }
1139
 
1140
    .brand-card:hover {
1141
      box-shadow: 0 3px 10px rgba(20, 30, 60, .06)
1142
    }
1143
 
1144
    .brand-card.open {
1145
      box-shadow: 0 4px 14px rgba(59, 91, 219, .14);
1146
      border-color: #c8d2f4
1147
    }
1148
 
1149
    .brand-head {
1150
      display: grid;
37460 ranu 1151
      grid-template-columns:24px 130px 1fr 96px 100px 60px;
37444 ranu 1152
      gap: 12px;
1153
      align-items: center;
1154
      padding: 10px 14px;
1155
      cursor: pointer;
1156
      user-select: none
1157
    }
1158
 
37460 ranu 1159
    .brand-head .bpc {
1160
      display: inline-flex;
1161
      align-items: center;
1162
      justify-content: center;
1163
      gap: 4px;
1164
      font-size: 11.5px;
1165
      font-weight: 700;
1166
      color: var(--brand);
1167
      background: #eef2ff;
1168
      border: 1px solid #d7defb;
1169
      border-radius: 999px;
1170
      padding: 3px 9px;
1171
      font-variant-numeric: tabular-nums;
1172
      white-space: nowrap;
1173
      justify-self: center
1174
    }
1175
 
1176
    .brand-head .bpc.zero {
1177
      color: var(--soft);
1178
      background: #f1f3f9;
1179
      border-color: var(--line)
1180
    }
1181
 
1182
    .brand-head .bpc svg {
1183
      width: 11px;
1184
      height: 11px
1185
    }
1186
 
37444 ranu 1187
    .brand-head .cx {
1188
      color: #c5cce0;
1189
      font-size: 14px;
1190
      transition: transform .18s ease, color .12s
1191
    }
1192
 
1193
    .brand-card.open .brand-head .cx {
1194
      transform: rotate(90deg);
1195
      color: var(--brand)
1196
    }
1197
 
1198
    .brand-head .bn {
1199
      font-size: 13px;
1200
      font-weight: 700;
1201
      color: var(--ink);
1202
      white-space: nowrap;
1203
      overflow: hidden;
1204
      text-overflow: ellipsis
1205
    }
1206
 
1207
    .brand-head .btrack {
1208
      height: 10px;
1209
      background: #eef1f6;
1210
      border-radius: 6px;
1211
      overflow: hidden
1212
    }
1213
 
1214
    .brand-head .btrack > i {
1215
      display: block;
1216
      height: 100%;
1217
      border-radius: 6px;
1218
      background: linear-gradient(90deg, var(--brand2), var(--brand));
1219
      transition: width .3s ease
1220
    }
1221
 
1222
    .brand-head .bval {
1223
      font-size: 13px;
1224
      font-weight: 700;
1225
      color: var(--ink);
1226
      font-variant-numeric: tabular-nums;
1227
      text-align: right
1228
    }
1229
 
1230
    .brand-head .bshare {
1231
      font-size: 12px;
1232
      font-weight: 700;
1233
      color: var(--muted);
1234
      font-variant-numeric: tabular-nums;
1235
      text-align: right
1236
    }
1237
 
1238
    .brand-card.top .brand-head .btrack > i {
1239
      background: linear-gradient(90deg, #38b2ac, var(--teal))
1240
    }
1241
 
1242
    .brand-card.top .brand-head .bshare {
1243
      color: var(--teal)
1244
    }
1245
 
1246
    .brand-body {
1247
      display: none;
1248
      padding: 6px 14px 14px;
1249
      border-top: 1px dashed var(--line);
1250
      background: #fafbfe
1251
    }
1252
 
1253
    .brand-card.open .brand-body {
1254
      display: block
1255
    }
1256
 
1257
    .brand-body .bhdr {
1258
      font-size: 10.5px;
1259
      letter-spacing: .06em;
1260
      text-transform: uppercase;
1261
      color: var(--soft);
1262
      font-weight: 700;
1263
      margin: 8px 0 8px
1264
    }
1265
 
1266
    .mbars {
1267
      display: flex;
1268
      flex-direction: column;
1269
      gap: 6px
1270
    }
1271
 
1272
    .mbar {
1273
      display: grid;
1274
      grid-template-columns:90px 1fr 100px 60px;
1275
      gap: 10px;
1276
      align-items: center;
1277
      padding: 6px 10px;
1278
      background: #fff;
1279
      border: 1px solid var(--line2);
1280
      border-radius: 8px
1281
    }
1282
 
1283
    .mbar .mname {
1284
      font-size: 11px;
1285
      font-weight: 700;
1286
      letter-spacing: .04em;
1287
      text-transform: uppercase;
1288
      display: flex;
1289
      align-items: center;
1290
      gap: 5px
1291
    }
1292
 
1293
    .mbar .mname i {
1294
      display: inline-block;
1295
      width: 7px;
1296
      height: 7px;
1297
      border-radius: 50%
1298
    }
1299
 
1300
    .mbar.hid .mname {
1301
      color: var(--hid)
1302
    }
1303
 
1304
    .mbar.hid .mname i {
1305
      background: var(--hid)
1306
    }
1307
 
1308
    .mbar.fastmoving .mname {
1309
      color: var(--fast)
1310
    }
1311
 
1312
    .mbar.fastmoving .mname i {
1313
      background: var(--fast)
1314
    }
1315
 
1316
    .mbar.running .mname {
1317
      color: var(--run)
1318
    }
1319
 
1320
    .mbar.running .mname i {
1321
      background: var(--run)
1322
    }
1323
 
1324
    .mbar.slowmoving .mname {
1325
      color: var(--slow)
1326
    }
1327
 
1328
    .mbar.slowmoving .mname i {
1329
      background: var(--slow)
1330
    }
1331
 
1332
    .mbar.other .mname {
1333
      color: var(--other)
1334
    }
1335
 
1336
    .mbar.other .mname i {
1337
      background: var(--other)
1338
    }
1339
 
1340
    .mbar .mtrack {
1341
      height: 10px;
1342
      border-radius: 5px;
1343
      overflow: hidden
1344
    }
1345
 
1346
    .mbar.hid .mtrack {
1347
      background: var(--hid-bg)
1348
    }
1349
 
1350
    .mbar.fastmoving .mtrack {
1351
      background: var(--fast-bg)
1352
    }
1353
 
1354
    .mbar.running .mtrack {
1355
      background: var(--run-bg)
1356
    }
1357
 
1358
    .mbar.slowmoving .mtrack {
1359
      background: var(--slow-bg)
1360
    }
1361
 
1362
    .mbar.other .mtrack {
1363
      background: var(--other-bg)
1364
    }
1365
 
1366
    .mbar .mtrack > i {
1367
      display: block;
1368
      height: 100%;
1369
      border-radius: 5px;
1370
      transition: width .3s ease
1371
    }
1372
 
1373
    .mbar.hid .mtrack > i {
1374
      background: linear-gradient(90deg, #9775fa, var(--hid))
1375
    }
1376
 
1377
    .mbar.fastmoving .mtrack > i {
1378
      background: linear-gradient(90deg, var(--brand2), var(--fast))
1379
    }
1380
 
1381
    .mbar.running .mtrack > i {
1382
      background: linear-gradient(90deg, #20c997, var(--run))
1383
    }
1384
 
1385
    .mbar.slowmoving .mtrack > i {
1386
      background: linear-gradient(90deg, #ffa94d, var(--slow))
1387
    }
1388
 
1389
    .mbar.other .mtrack > i {
1390
      background: linear-gradient(90deg, #adb5bd, var(--other))
1391
    }
1392
 
1393
    .mbar .mval {
1394
      font-size: 12px;
1395
      font-weight: 650;
1396
      color: var(--ink);
1397
      font-variant-numeric: tabular-nums;
1398
      text-align: right
1399
    }
1400
 
1401
    .mbar .mshare {
1402
      font-size: 12px;
1403
      font-weight: 700;
1404
      font-variant-numeric: tabular-nums;
1405
      text-align: right;
1406
      color: var(--muted)
1407
    }
1408
 
1409
    /* status list modal (achieved vs pending) */
1410
    .status-back {
1411
      position: fixed;
1412
      inset: 0;
1413
      background: rgba(15, 22, 40, .5);
1414
      backdrop-filter: blur(2px);
1415
      z-index: 1300;
1416
      display: none;
1417
      align-items: flex-start;
1418
      justify-content: center;
1419
      padding: 60px 20px;
1420
      overflow-y: auto
1421
    }
1422
 
1423
    .status-back.show {
1424
      display: flex
1425
    }
1426
 
1427
    .status-modal {
1428
      background: #fff;
1429
      border-radius: 16px;
1430
      max-width: 1000px;
1431
      width: 100%;
1432
      box-shadow: 0 20px 60px rgba(20, 30, 60, .28);
1433
      padding: 22px 24px 20px;
1434
      position: relative
1435
    }
1436
 
1437
    .status-modal .x {
1438
      position: absolute;
1439
      top: 14px;
1440
      right: 16px;
1441
      cursor: pointer;
1442
      color: var(--soft);
1443
      font-size: 24px;
1444
      line-height: 1;
1445
      border: 0;
1446
      background: none;
1447
      width: 32px;
1448
      height: 32px;
1449
      border-radius: 8px;
1450
      transition: background .1s
1451
    }
1452
 
1453
    .status-modal .x:hover {
1454
      background: #f1f3f9;
1455
      color: var(--ink)
1456
    }
1457
 
1458
    .status-modal h3 {
1459
      margin: 0 0 3px;
1460
      font-size: 19px;
1461
      font-weight: 760;
1462
      letter-spacing: -.01em;
1463
      padding-right: 40px
1464
    }
1465
 
1466
    .status-modal > .desc {
1467
      margin: 0 0 18px;
1468
      font-size: 12.5px;
1469
      color: var(--muted);
1470
      font-weight: 600
1471
    }
1472
 
1473
    .status-grid {
1474
      display: grid;
1475
      grid-template-columns:1fr 1fr;
1476
      gap: 18px
1477
    }
1478
 
1479
    .status-col {
1480
      border: 1px solid var(--line);
1481
      border-radius: 12px;
1482
      padding: 14px 16px;
1483
      background: #fbfcfe
1484
    }
1485
 
1486
    .status-col.good {
1487
      border-color: #c3e8d0;
1488
      background: #f4fbf6
1489
    }
1490
 
1491
    .status-col.bad {
1492
      border-color: #f2c4c4;
1493
      background: #fdf5f5
1494
    }
1495
 
1496
    .status-col h4 {
1497
      margin: 0 0 10px;
1498
      font-size: 13px;
1499
      font-weight: 730;
1500
      display: flex;
1501
      align-items: center;
1502
      gap: 8px;
1503
      letter-spacing: -.005em
1504
    }
1505
 
1506
    .status-col h4 .cnt {
1507
      background: #fff;
1508
      border: 1px solid var(--line);
1509
      color: var(--ink);
1510
      font-size: 11px;
1511
      font-weight: 700;
1512
      padding: 2px 9px;
1513
      border-radius: 999px
1514
    }
1515
 
1516
    .status-col.good h4 {
1517
      color: #177a3a
1518
    }
1519
 
1520
    .status-col.good h4 .cnt {
1521
      background: #e5f7ee;
1522
      border-color: #c3e8d0;
1523
      color: #177a3a
1524
    }
1525
 
1526
    .status-col.bad h4 {
1527
      color: #b8272b
1528
    }
1529
 
1530
    .status-col.bad h4 .cnt {
1531
      background: #ffeaea;
1532
      border-color: #f2c4c4;
1533
      color: #b8272b
1534
    }
1535
 
1536
    .status-list {
1537
      display: flex;
1538
      flex-direction: column;
1539
      gap: 6px;
1540
      max-height: 60vh;
1541
      overflow-y: auto;
1542
      padding-right: 4px
1543
    }
1544
 
1545
    .status-item {
1546
      display: grid;
1547
      grid-template-columns:1fr 100px 68px;
1548
      gap: 10px;
1549
      align-items: center;
1550
      padding: 9px 12px;
1551
      background: #fff;
1552
      border: 1px solid var(--line2);
1553
      border-radius: 9px;
1554
      transition: box-shadow .12s
1555
    }
1556
 
1557
    .status-item:hover {
1558
      box-shadow: 0 2px 8px rgba(20, 30, 60, .06)
1559
    }
1560
 
1561
    .status-item .rn {
1562
      font-weight: 700;
1563
      color: var(--ink);
1564
      font-size: 12.5px;
1565
      line-height: 1.3
1566
    }
1567
 
1568
    .status-item .wh {
1569
      font-size: 11px;
1570
      color: var(--muted);
1571
      font-weight: 600;
1572
      display: block;
1573
      margin-top: 2px
1574
    }
1575
 
1576
    .status-item .vv {
1577
      font-size: 12px;
1578
      color: var(--muted);
1579
      font-variant-numeric: tabular-nums;
1580
      font-weight: 600;
1581
      text-align: right;
1582
      white-space: nowrap
1583
    }
1584
 
1585
    .status-item .vv b {
1586
      color: var(--ink);
1587
      font-weight: 700
1588
    }
1589
 
1590
    .status-item .pp {
1591
      font-size: 12px;
1592
      font-weight: 700;
1593
      font-variant-numeric: tabular-nums;
1594
      text-align: right
1595
    }
1596
 
1597
    .status-item .pp.good {
1598
      color: var(--good)
1599
    }
1600
 
1601
    .status-item .pp.warn {
1602
      color: var(--warn)
1603
    }
1604
 
1605
    .status-item .pp.bad {
1606
      color: var(--bad)
1607
    }
1608
 
1609
    .status-empty {
1610
      padding: 24px;
1611
      text-align: center;
1612
      color: var(--soft);
1613
      font-size: 12.5px
1614
    }
1615
 
37460 ranu 1616
    /* extras: aging + partner-billing tables */
1617
    .xtable {
1618
      width: 100%;
1619
      border-collapse: separate;
1620
      border-spacing: 0;
1621
      font-size: 13px
1622
    }
1623
 
1624
    .xtable th {
1625
      font-size: 10.5px;
1626
      text-transform: uppercase;
1627
      letter-spacing: .06em;
1628
      color: var(--soft);
1629
      font-weight: 700;
1630
      white-space: nowrap;
1631
      padding: 10px 10px;
1632
      border-bottom: 1px solid var(--line);
1633
      text-align: left;
1634
      background: #fff
1635
    }
1636
 
1637
    .xtable th.num {
1638
      text-align: right
1639
    }
1640
 
1641
    .xtable td {
1642
      padding: 10px;
1643
      border-bottom: 1px solid var(--line2);
1644
      color: var(--ink);
1645
      font-weight: 500
1646
    }
1647
 
1648
    .xtable td.num {
1649
      text-align: right;
1650
      font-variant-numeric: tabular-nums;
1651
      font-weight: 650
1652
    }
1653
 
1654
    .xtable tbody tr:hover {
1655
      background: #f6f8fd
1656
    }
1657
 
1658
    .xtable .rbm-nm {
1659
      font-weight: 700;
1660
      color: var(--ink)
1661
    }
1662
 
1663
    .xtable tr.tot td {
1664
      font-weight: 770;
1665
      background: #fafbfe;
1666
      border-top: 2px solid var(--line);
1667
      color: var(--ink)
1668
    }
1669
 
1670
    .xtable tr.bal td {
1671
      font-weight: 700;
1672
      background: #fffbf2;
1673
      border-top: 1px dashed #f0d68a;
1674
      color: #7a5a00
1675
    }
1676
 
1677
    .xtable tr.bal td.hdr {
1678
      font-weight: 770
1679
    }
1680
 
1681
    .xtable td.g-hid {
1682
      border-left: 2px solid transparent
1683
    }
1684
 
1685
    /* placeholder for future column tint */
1686
    .xtable th.g-hid, .xtable td.g-hid {
1687
      color: var(--hid)
1688
    }
1689
 
1690
    .xtable th.g-fast, .xtable td.g-fast {
1691
      color: var(--fast)
1692
    }
1693
 
1694
    .xtable th.g-run, .xtable td.g-run {
1695
      color: var(--run)
1696
    }
1697
 
1698
    .xtable th.g-slow, .xtable td.g-slow {
1699
      color: var(--slow)
1700
    }
1701
 
1702
    .xtable th.g-other, .xtable td.g-other {
1703
      color: var(--other)
1704
    }
1705
 
1706
    .xtable th.g-total, .xtable td.g-total {
1707
      color: var(--brand);
1708
      font-weight: 700
1709
    }
1710
 
1711
    .xtable td.g-hid, .xtable td.g-fast, .xtable td.g-run, .xtable td.g-slow, .xtable td.g-other {
1712
      opacity: .95
1713
    }
1714
 
1715
    .xtable td.zero {
1716
      color: var(--soft);
1717
      font-weight: 500
1718
    }
1719
 
1720
    .xtable .cnt-pill {
1721
      display: inline-block;
1722
      min-width: 32px;
1723
      padding: 2px 8px;
1724
      border-radius: 6px;
1725
      font-size: 11.5px;
1726
      font-weight: 700;
1727
      font-variant-numeric: tabular-nums;
1728
      background: #eef2ff;
1729
      color: var(--brand)
1730
    }
1731
 
1732
    .xtable .cnt-pill.zero {
1733
      background: #f1f3f9;
1734
      color: var(--soft)
1735
    }
1736
 
1737
    .xtable .cnt-pill.warn {
1738
      background: #fff0e0;
1739
      color: #a04600
1740
    }
1741
 
1742
    .xtable .cnt-pill.good {
1743
      background: #e5f7ee;
1744
      color: #177a3a
1745
    }
1746
 
37469 ranu 1747
    /* call-target drawer content (Called Partners / OOS Calls) */
1748
    #rdrrCallDrawerBody {
1749
      overflow-x: auto
1750
    }
1751
 
1752
    #rdrrCallDrawerBody table {
1753
      width: 100%;
1754
      border-collapse: separate;
1755
      border-spacing: 0;
1756
      font-size: 12.5px;
1757
      background: #fff;
1758
      margin: 0
1759
    }
1760
 
1761
    #rdrrCallDrawerBody table th {
1762
      font-size: 10.5px;
1763
      text-transform: uppercase;
1764
      letter-spacing: .04em;
1765
      color: var(--soft);
1766
      font-weight: 700;
1767
      padding: 9px 10px;
1768
      background: #fafbfe;
1769
      border-bottom: 1px solid var(--line);
1770
      text-align: left;
1771
      white-space: nowrap
1772
    }
1773
 
1774
    #rdrrCallDrawerBody table td {
1775
      padding: 9px 10px;
1776
      border-bottom: 1px solid var(--line2);
1777
      color: var(--ink);
1778
      font-weight: 500;
1779
      vertical-align: middle
1780
    }
1781
 
1782
    #rdrrCallDrawerBody table tr:hover {
1783
      background: #f6f8fd
1784
    }
1785
 
1786
    #rdrrCallDrawerBody audio {
1787
      max-width: 170px;
1788
      height: 26px
1789
    }
1790
 
37468 ranu 1791
    /* rbm performance dashboard (extracted from today_po_rbm) */
1792
    #rdrrPerfWrap {
1793
      overflow-x: auto
1794
    }
1795
 
1796
    #rdrrPerfWrap table {
1797
      width: 100%;
1798
      border-collapse: separate;
1799
      border-spacing: 0;
1800
      font-size: 12.5px;
1801
      background: #fff
1802
    }
1803
 
1804
    #rdrrPerfWrap table th, #rdrrPerfWrap table td {
1805
      padding: 9px 10px;
1806
      text-align: center;
1807
      vertical-align: middle;
1808
      border-bottom: 1px solid var(--line2);
1809
      color: var(--ink);
1810
      font-weight: 500
1811
    }
1812
 
1813
    #rdrrPerfWrap table thead th {
1814
      font-size: 10.5px;
1815
      text-transform: uppercase;
1816
      letter-spacing: .05em;
1817
      font-weight: 700;
1818
      background: #1d2433;
1819
      color: #fff;
1820
      border-bottom: 0;
1821
      padding: 10px 8px;
1822
      line-height: 1.2
1823
    }
1824
 
1825
    #rdrrPerfWrap table thead th small {
1826
      font-size: 9.5px;
1827
      color: rgba(255, 255, 255, .7);
1828
      font-weight: 600;
1829
      display: block;
1830
      margin-top: 2px
1831
    }
1832
 
1833
    /* per-column accent badges (keep original palette from today_po_rbm) */
1834
    #rdrrPerfWrap table thead th:nth-child(4) {
1835
      background: linear-gradient(180deg, #40c057, #2f9e44)
1836
    }
1837
 
1838
    #rdrrPerfWrap table thead th:nth-child(5) {
1839
      background: linear-gradient(180deg, #22b8cf, #0e959f)
1840
    }
1841
 
1842
    #rdrrPerfWrap table thead th:nth-child(6) {
1843
      background: linear-gradient(180deg, #ffa94d, #e8590c)
1844
    }
1845
 
1846
    #rdrrPerfWrap table thead th:nth-child(7) {
1847
      background: linear-gradient(180deg, #9775fa, #7048e8)
1848
    }
1849
 
1850
    #rdrrPerfWrap table thead th:nth-child(8) {
1851
      background: linear-gradient(180deg, #ffd43b, #e8ac00);
1852
      color: #3a2500
1853
    }
1854
 
1855
    #rdrrPerfWrap table thead th:nth-child(8) small {
1856
      color: rgba(58, 37, 0, .75)
1857
    }
1858
 
1859
    #rdrrPerfWrap table thead th:nth-child(9) {
1860
      background: linear-gradient(180deg, #ff922b, #e8590c)
1861
    }
1862
 
1863
    #rdrrPerfWrap table thead th:last-child {
1864
      background: linear-gradient(180deg, #ff6b6b, #e03131)
1865
    }
1866
 
1867
    #rdrrPerfWrap table tbody tr:hover {
1868
      background: #f6f8fd
1869
    }
1870
 
1871
    /* rank rows keep their original background tint from today_po_rbm inline style */
1872
    #rdrrPerfWrap table tbody tr td:nth-child(2) {
1873
      text-align: left;
1874
      font-weight: 700;
1875
      color: var(--ink)
1876
    }
1877
 
1878
    #rdrrPerfWrap table tbody tr td:first-child img {
1879
      width: 24px;
1880
      height: 24px
1881
    }
1882
 
1883
    #rdrrPerfWrap table tbody tr:first-child td {
1884
      font-weight: 700
1885
    }
1886
 
1887
    #rdrrPerfWrap table tbody tr td:last-child {
1888
      font-weight: 770;
1889
      font-size: 13.5px;
1890
      font-variant-numeric: tabular-nums
1891
    }
1892
 
37522 ranu 1893
    /* purchase-milestones (brand × person) — compact heatmap grid, custom-rendered client-side */
37468 ranu 1894
    #rdrrPmWrap {
37522 ranu 1895
      overflow: hidden
37468 ranu 1896
    }
1897
 
37522 ranu 1898
    #rdrrPmWrap .pm-grid-wrap {
37561 ranu 1899
      overflow: auto;
1900
      max-height: 720px;
37522 ranu 1901
      border: 1px solid var(--line);
1902
      border-radius: 10px;
1903
      background: #fff
1904
    }
1905
 
1906
    #rdrrPmWrap .pm-grid {
37468 ranu 1907
      border-collapse: separate;
1908
      border-spacing: 0;
37561 ranu 1909
      font-size: 13px;
37468 ranu 1910
      background: #fff
1911
    }
1912
 
37522 ranu 1913
    /* --- header (brand strip) --- */
1914
    #rdrrPmWrap .pm-grid thead th {
37561 ranu 1915
      position: sticky;
1916
      top: 0;
1917
      z-index: 3;
1918
      padding: 10px 14px;
1919
      min-width: 140px;
37522 ranu 1920
      color: #fff;
1921
      font-weight: 700;
37523 ranu 1922
      letter-spacing: .05em;
37561 ranu 1923
      font-size: 11px;
37522 ranu 1924
      text-transform: uppercase;
1925
      text-align: center;
1926
      white-space: nowrap;
1927
      background: linear-gradient(180deg, var(--brand2), var(--brand));
37523 ranu 1928
      border-bottom: 2px solid rgba(0, 0, 0, .08)
37522 ranu 1929
    }
1930
 
1931
    #rdrrPmWrap .pm-grid thead th.pm-corner {
37561 ranu 1932
      position: sticky;
1933
      left: 0;
1934
      z-index: 5;
1935
      min-width: 170px;
37522 ranu 1936
      background: #fff;
37468 ranu 1937
      color: var(--ink);
37522 ranu 1938
      text-align: left;
1939
      border-right: 1px solid var(--line);
37561 ranu 1940
      padding: 10px 14px
37468 ranu 1941
    }
1942
 
37522 ranu 1943
    #rdrrPmWrap .pm-corner-lbl {
37523 ranu 1944
      font-size: 11.5px;
37522 ranu 1945
      font-weight: 700;
1946
      letter-spacing: .05em;
1947
      color: var(--ink)
1948
    }
1949
 
1950
    #rdrrPmWrap .pm-corner-hint {
37523 ranu 1951
      font-size: 10px;
37522 ranu 1952
      color: var(--soft);
1953
      font-weight: 500;
1954
      letter-spacing: .02em;
1955
      text-transform: none;
37523 ranu 1956
      margin-top: 1px
37522 ranu 1957
    }
1958
 
37523 ranu 1959
    /* --- name column --- */
37522 ranu 1960
    #rdrrPmWrap .pm-name {
37561 ranu 1961
      position: sticky;
1962
      left: 0;
1963
      z-index: 2;
37576 ranu 1964
      min-width: 140px;
37468 ranu 1965
      background: #fff;
37576 ranu 1966
      padding: 7px 5px;
37522 ranu 1967
      border-right: 1px solid var(--line);
1968
      vertical-align: middle
37468 ranu 1969
    }
1970
 
37522 ranu 1971
    #rdrrPmWrap .pm-name-txt {
1972
      font-weight: 650;
37576 ranu 1973
      font-size: 16px;
37522 ranu 1974
      color: var(--ink);
1975
      line-height: 1.2;
1976
      white-space: nowrap;
1977
      overflow: hidden;
1978
      text-overflow: ellipsis
1979
    }
1980
 
1981
    #rdrrPmWrap .pm-name-sub {
37523 ranu 1982
      font-size: 9.5px;
37468 ranu 1983
      color: var(--soft);
37523 ranu 1984
      margin-top: 1px;
37522 ranu 1985
      letter-spacing: .04em;
1986
      text-transform: uppercase;
1987
      font-weight: 600
1988
    }
1989
 
1990
    /* --- data cells --- */
1991
    #rdrrPmWrap .pm-cell {
37576 ranu 1992
      padding: 5px 7px;
1993
      min-width: 140px;
37522 ranu 1994
      text-align: right;
1995
      vertical-align: middle;
1996
      font-variant-numeric: tabular-nums;
37561 ranu 1997
      color: var(--ink)
37522 ranu 1998
    }
1999
 
2000
    #rdrrPmWrap .pm-cell-zero {
2001
      color: #cbd0d9;
37570 ranu 2002
      font-size: 17px;
37523 ranu 2003
      font-weight: 500;
2004
      text-align: center
37522 ranu 2005
    }
2006
 
37570 ranu 2007
    /* Stacked cell content: partner count on top, PO in middle, Billed at bottom (primary) */
2008
    #rdrrPmWrap .pm-partners {
2009
      font-size: 12px;
37573 ranu 2010
      font-weight: 700;
37582 ranu 2011
      color: #000;
37573 ranu 2012
      line-height: 1.2;
37570 ranu 2013
      margin-bottom: 4px;
37523 ranu 2014
      white-space: nowrap
37468 ranu 2015
    }
2016
 
37570 ranu 2017
    #rdrrPmWrap .pm-po {
37573 ranu 2018
      font-size: 14px;
2019
      font-weight: 600;
37582 ranu 2020
      color: #000;
37573 ranu 2021
      line-height: 1.2;
37570 ranu 2022
      margin-bottom: 4px;
37561 ranu 2023
      white-space: nowrap
37468 ranu 2024
    }
2025
 
37570 ranu 2026
    #rdrrPmWrap .pm-billed {
2027
      font-weight: 800;
37576 ranu 2028
      font-size: 16px;
37570 ranu 2029
      line-height: 1.2;
2030
      color: var(--ink);
2031
      white-space: nowrap
37468 ranu 2032
    }
2033
 
37570 ranu 2034
    #rdrrPmWrap .pm-tag {
37576 ranu 2035
      font-size: 12px;
37570 ranu 2036
      font-weight: 500;
37573 ranu 2037
      opacity: .6;
37570 ranu 2038
      margin-left: 2px;
37573 ranu 2039
      letter-spacing: .02em;
37582 ranu 2040
      color: #000;
37523 ranu 2041
    }
2042
 
2043
    /* --- row hover --- */
2044
    #rdrrPmWrap .pm-grid tbody tr.pm-row + tr.pm-row td {
2045
      border-top: 1px solid var(--line2)
37468 ranu 2046
    }
2047
 
37523 ranu 2048
    #rdrrPmWrap .pm-grid tbody tr.pm-row:hover .pm-name {
2049
      background: #eef2fb
2050
    }
2051
 
37522 ranu 2052
    /* --- totals row (pinned at top, distinct) --- */
2053
    #rdrrPmWrap .pm-total-row .pm-name {
2054
      background: linear-gradient(180deg, #f2f5fb, #eaeff9);
2055
      border-right: 1px solid var(--line);
2056
      border-bottom: 2px solid var(--brand)
37468 ranu 2057
    }
2058
 
37522 ranu 2059
    #rdrrPmWrap .pm-total-row .pm-name-txt {
2060
      color: var(--brand);
2061
      font-weight: 800
37468 ranu 2062
    }
2063
 
37522 ranu 2064
    #rdrrPmWrap .pm-total-row .pm-name-sub {
2065
      color: var(--brand);
2066
      opacity: .8
37468 ranu 2067
    }
2068
 
37522 ranu 2069
    #rdrrPmWrap .pm-cell-total {
2070
      background: linear-gradient(180deg, #f2f5fb, #eaeff9) !important;
2071
      border-bottom: 2px solid var(--brand)
2072
    }
2073
 
2074
    #rdrrPmWrap .pm-cell-total .pm-billed {
2075
      color: var(--brand);
2076
      font-weight: 800;
37523 ranu 2077
      font-size: 13px
37522 ranu 2078
    }
2079
 
2080
    /* --- brand accent header gradients (slug matches JS output) --- */
2081
    #rdrrPmWrap .pm-grid thead th.total {
2082
      background: linear-gradient(180deg, #3b5bdb, #28449e)
2083
    }
2084
 
2085
    #rdrrPmWrap .pm-grid thead th.vivo {
37468 ranu 2086
      background: linear-gradient(180deg, #00baf2, #0a8fb5)
2087
    }
2088
 
37522 ranu 2089
    #rdrrPmWrap .pm-grid thead th.oppo {
37468 ranu 2090
      background: linear-gradient(180deg, #20a558, #177a3a)
2091
    }
2092
 
37522 ranu 2093
    #rdrrPmWrap .pm-grid thead th.samsung {
37468 ranu 2094
      background: linear-gradient(180deg, #0b3d91, #071e51)
2095
    }
2096
 
37522 ranu 2097
    #rdrrPmWrap .pm-grid thead th.alcatel {
37468 ranu 2098
      background: linear-gradient(180deg, #5c7cfa, #3b5bdb)
2099
    }
2100
 
37522 ranu 2101
    #rdrrPmWrap .pm-grid thead th.motorola {
37468 ranu 2102
      background: linear-gradient(180deg, #495057, #212529)
2103
    }
2104
 
37522 ranu 2105
    #rdrrPmWrap .pm-grid thead th.hmd {
37468 ranu 2106
      background: linear-gradient(180deg, #7048e8, #5b34c9)
2107
    }
2108
 
37522 ranu 2109
    #rdrrPmWrap .pm-grid thead th.xiaomi {
37468 ranu 2110
      background: linear-gradient(180deg, #ffa726, #e8590c)
2111
    }
2112
 
37522 ranu 2113
    #rdrrPmWrap .pm-grid thead th.lava {
37468 ranu 2114
      background: linear-gradient(180deg, #e03131, #a11515)
2115
    }
2116
 
37522 ranu 2117
    #rdrrPmWrap .pm-grid thead th.poco {
37468 ranu 2118
      background: linear-gradient(180deg, #ffd43b, #e8ac00);
2119
      color: #3a2500
2120
    }
2121
 
37522 ranu 2122
    #rdrrPmWrap .pm-grid thead th.realme {
37468 ranu 2123
      background: linear-gradient(180deg, #f59f00, #e8590c)
2124
    }
2125
 
37522 ranu 2126
    #rdrrPmWrap .pm-grid thead th.apple {
37468 ranu 2127
      background: linear-gradient(180deg, #495057, #141a2b)
2128
    }
2129
 
37522 ranu 2130
    #rdrrPmWrap .pm-grid thead th.oneplus {
37468 ranu 2131
      background: linear-gradient(180deg, #e03131, #a11515)
2132
    }
2133
 
37522 ranu 2134
    #rdrrPmWrap .pm-grid thead th.tecno {
37468 ranu 2135
      background: linear-gradient(180deg, #0ca678, #087a56)
2136
    }
2137
 
37444 ranu 2138
    @media (max-width: 960px) {
2139
      .kpis {
2140
        grid-template-columns:repeat(2, 1fr)
2141
      }
2142
 
2143
      .brand h1 {
2144
        font-size: 22px
2145
      }
2146
 
2147
      .status-grid {
2148
        grid-template-columns:1fr
2149
      }
2150
    }
2151
  </style>
2152
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css"/>
2153
</head>
2154
<body>
2155
<div class="wrap">
2156
  <header class="top">
2157
    <div class="brand">
2158
      <div class="crumb"><a href="/indent/today_po_rbm">&larr; Today PO / RBM</a> &middot; Sales &middot; Indent</div>
2159
      <h1>RBM DRR Dashboard <span class="live"><i></i>Live</span></h1>
2160
    </div>
2161
    <div class="hd-right">
37497 ranu 2162
      <select id="rdrrTypeSelect" class="ctl" style="min-width:120px;height:38px;padding:8px 12px" title="Filter type">
2163
        <option value="RBM" selected>RBM</option>
2164
        <option value="SALES">Sales</option>
2165
      </select>
2166
      <select id="rdrrLevelSelect" class="ctl" style="min-width:140px;height:38px;padding:8px 12px;display:none"
2167
              title="Sales level">
2168
        <option value="">Select level</option>
2169
        <option value="ASM">ASM (L1)</option>
2170
        <option value="CM">CM (L2)</option>
2171
        <option value="DM">DM (L3)</option>
2172
        <option value="BM">BM (L4)</option>
2173
        <option value="RSM">RSM (L5)</option>
2174
        <option value="NSM">NSM (L6)</option>
2175
      </select>
2176
      <select id="rdrrPersonSelect" class="ctl" style="min-width:230px;height:38px;padding:8px 12px" title="Person">
37487 ranu 2177
        <option value="">All RBMs</option>
2178
      </select>
37444 ranu 2179
      <input class="ctl" id="rdrrPeriod" type="text" placeholder="Select date range"/>
2180
      <button class="btn-r" id="rdrrApply">Apply</button>
2181
    </div>
2182
  </header>
2183
 
2184
  <div class="kpis">
2185
    <div class="kpi a">
2186
      <div class="top">
2187
        <div class="ic">
2188
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"
2189
               stroke-linejoin="round">
2190
            <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
2191
          </svg>
2192
        </div>
2193
        <div class="label">Total Achieved</div>
2194
      </div>
2195
      <div class="val" id="rdrrKpiAch">&mdash;</div>
2196
      <div class="foot" id="rdrrKpiAchSub">Actual billed across all RBMs</div>
2197
    </div>
2198
    <div class="kpi b">
2199
      <div class="top">
2200
        <div class="ic">
2201
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"
2202
               stroke-linejoin="round">
2203
            <circle cx="12" cy="12" r="9"/>
2204
            <circle cx="12" cy="12" r="5"/>
2205
            <circle cx="12" cy="12" r="1.5" fill="currentColor"/>
2206
          </svg>
2207
        </div>
2208
        <div class="label">Total Target</div>
2209
      </div>
2210
      <div class="val" id="rdrrKpiTgt">&mdash;</div>
2211
      <div class="foot" id="rdrrKpiTgtSub">Sum of daily target snapshots</div>
2212
    </div>
2213
    <div class="kpi c">
2214
      <div class="top">
2215
        <div class="ic">
2216
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"
2217
               stroke-linejoin="round">
2218
            <path d="M3 17l6-6 4 4 8-8"/>
2219
            <path d="M17 7h4v4"/>
2220
          </svg>
2221
        </div>
2222
        <div class="label">Achievement %</div>
2223
      </div>
2224
      <div class="val" id="rdrrKpiPct">&mdash;</div>
2225
      <div class="foot" id="rdrrKpiPctSub">Total ACH / Total TGT</div>
2226
    </div>
2227
    <div class="kpi d clickable" id="rdrrKpiTopCard" title="Click to see achieved vs pending RBMs">
2228
      <div class="top">
2229
        <div class="ic">
2230
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"
2231
               stroke-linejoin="round">
2232
            <path d="M12 2l3 6.5 7 1-5 5 1.5 7L12 18l-6.5 3.5L7 14.5 2 9.5l7-1L12 2z"/>
2233
          </svg>
2234
        </div>
2235
        <div class="label">RBMs ≥ 100% Target</div>
2236
      </div>
2237
      <div class="val" id="rdrrKpiTop">&mdash;</div>
2238
      <div class="foot" id="rdrrKpiTopSub">Rows meeting or beating target</div>
2239
      <span class="hint">Click for list &rsaquo;</span>
2240
    </div>
2241
  </div>
2242
 
2243
  <div class="sectionlabel">RBM &times; Warehouse &times; Movement Bucket</div>
2244
  <div class="legend">
2245
    <span class="lg hid"><i></i><span class="lgt">HID</span></span>
2246
    <span class="lg fast"><i></i><span class="lgt">FAST</span></span>
2247
    <span class="lg run"><i></i><span class="lgt">RUN</span></span>
2248
    <span class="lg slow"><i></i><span class="lgt">SLOW</span></span>
2249
    <span class="lg other"><i></i><span class="lgt">OTHER</span></span>
2250
  </div>
2251
  <div class="card">
2252
    <div class="card-hd">
2253
      <div>
2254
        <h2>Achievement vs Target</h2>
2255
        <p class="desc">Click any row for a per-bucket breakdown and the catalogs sold.</p>
2256
      </div>
2257
      <span class="badge-period" id="rdrrPeriodLabel">&mdash;</span>
2258
    </div>
2259
    <div id="rdrrTableWrap">
2260
      <div class="loading">
2261
        <div class="spinner"></div>
2262
        Loading&hellip;
2263
      </div>
2264
    </div>
2265
    <div class="foot-note">
2266
      <b>TGT</b> is summed from persisted per-day snapshots (<code>inventory.rbm_targets</code>) — days with no
2267
      scheduler-written snapshot contribute 0.
2268
      <b>ACH</b> is computed from source orders (<code>transaction.order</code>) for the range.
2269
    </div>
2270
  </div>
37460 ranu 2271
 
2272
  <!-- >15 Days Aging Sold -->
2273
  <div class="sectionlabel">&gt; 15 Days Aging Sold</div>
2274
  <div class="card">
2275
    <div class="card-hd">
2276
      <div>
2277
        <h2>Aging Inventory Cleared</h2>
2278
        <p class="desc">Billed value of items that were &gt; 15 days old at sale, per RBM, for the picked range.</p>
2279
      </div>
2280
      <span class="badge-period" id="rdrrAgingPeriod">&mdash;</span>
2281
    </div>
2282
    <div id="rdrrAgingWrap">
2283
      <div class="loading">
2284
        <div class="spinner"></div>
2285
        Loading&hellip;
2286
      </div>
2287
    </div>
2288
  </div>
2289
 
2290
  <!-- Partner Billing Count -->
2291
  <div class="sectionlabel">Partner Billing Count <span
2292
          style="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(Target: &gt;1L, Non-Target: &gt;20K)</span>
2293
  </div>
2294
  <div class="card">
2295
    <div class="card-hd">
2296
      <div>
2297
        <h2>Partners Who Billed This Month</h2>
2298
        <p class="desc">Counts partners meeting the billing threshold in each week bucket. Always month-to-date (weekly
2299
          buckets are inherently monthly).</p>
2300
      </div>
2301
      <span class="badge-period" id="rdrrBillingPeriod">&mdash;</span>
2302
    </div>
2303
    <div id="rdrrBillingWrap">
2304
      <div class="loading">
2305
        <div class="spinner"></div>
2306
        Loading&hellip;
2307
      </div>
2308
    </div>
2309
  </div>
37468 ranu 2310
 
2311
  <!-- RBM Performance Dashboard (MTD) — extracted from /indent/today_po_rbm without any backend change -->
2312
  <div class="sectionlabel">RBM Performance Dashboard (MTD)</div>
2313
  <div class="card">
2314
    <div class="card-hd">
2315
      <div>
2316
        <h2>Composite MTD Ranking</h2>
2317
        <p class="desc">Rank of RBMs by weighted score across Talk Time (10%) + MTD Achievement (55%) + Aging (2%) +
2318
          &gt;1L Partners (29%) + Rating (2%) + Partners Rated (2%).</p>
2319
      </div>
2320
      <span class="badge-period" id="rdrrPerfMonth">MTD</span>
2321
    </div>
2322
    <div id="rdrrPerfWrap">
2323
      <div class="loading">
2324
        <div class="spinner"></div>
2325
        Loading&hellip;
2326
      </div>
2327
    </div>
2328
  </div>
2329
 
2330
  <!-- Today PO & Billed — Brand × RBM matrix (reuses /indent/today_po_table_fragment) -->
2331
  <div class="sectionlabel">Today PO &amp; Billed — Brand &times; RBM</div>
2332
  <div class="card">
2333
    <div class="card-hd">
2334
      <div>
2335
        <h2>Purchase Milestones by Brand</h2>
2336
        <p class="desc">Today's PO value / partner count / total billed, per RBM &times; brand. Always today
2337
          (independent of the range picker above).</p>
2338
      </div>
2339
      <select id="rdrrPmCategory" class="ctl" style="min-width:170px;height:34px;padding:6px 12px">
2340
        <option value="RBM">All RBMs</option>
2341
        <option value="NSM">All NSMs</option>
2342
        <option value="RSM">All RSMs</option>
2343
        <option value="BM">All BMs</option>
2344
        <option value="DM">All DMs</option>
2345
        <option value="CM">All CMs</option>
2346
        <option value="ASM">All ASMs</option>
2347
      </select>
2348
    </div>
2349
    <div id="rdrrPmWrap">
2350
      <div class="loading">
2351
        <div class="spinner"></div>
2352
        Loading&hellip;
2353
      </div>
2354
    </div>
2355
  </div>
37469 ranu 2356
 
2357
  <!-- RBM Call Target Summary — full interactive page injected via iframe srcdoc (no backend change) -->
2358
  <div class="sectionlabel">RBM Call Target Summary</div>
2359
  <div class="card">
2360
    <div class="card-hd">
2361
      <div>
2362
        <h2>Today's Call Coverage per RBM</h2>
2363
        <p class="desc">Full interactive view — all popups, drill-downs and downloads work exactly like the standalone
2364
          page.</p>
2365
      </div>
2366
      <a class="badge-period" href="/indent/rbm_call_target" target="_blank" style="text-decoration:none">Open in new
2367
        tab &rsaquo;</a>
2368
    </div>
2369
    <iframe id="rdrrCallFrame" title="RBM Call Target Summary"
2370
            style="width:100%;min-height:800px;border:0;border-radius:10px;background:#fff;display:block"></iframe>
2371
  </div>
37444 ranu 2372
</div>
2373
 
2374
<!-- achieved-vs-pending modal (opens from the RBMs ≥ 100% Target KPI card) -->
2375
<div class="status-back" id="rdrrStatusBack">
2376
  <div class="status-modal">
2377
    <button class="x" id="rdrrStatusClose" aria-label="Close">&times;</button>
2378
    <h3>Target Status by RBM</h3>
2379
    <p class="desc" id="rdrrStatusDesc">&mdash;</p>
2380
    <div class="status-grid">
2381
      <div class="status-col good">
2382
        <h4>✅ Achieved <span class="cnt" id="rdrrStatusAchCnt">0</span></h4>
2383
        <div class="status-list" id="rdrrStatusAchList"></div>
2384
      </div>
2385
      <div class="status-col bad">
2386
        <h4>⚠ Pending <span class="cnt" id="rdrrStatusPndCnt">0</span></h4>
2387
        <div class="status-list" id="rdrrStatusPndList"></div>
2388
      </div>
2389
    </div>
2390
  </div>
2391
</div>
2392
 
37469 ranu 2393
<!-- call-target drill-down drawer (Called Partners / OOS Calls) -->
2394
<div class="rdrr-back" id="rdrrCallDrawerBack">
2395
  <div class="rdrr-panel">
2396
    <button class="x" id="rdrrCallDrawerClose" aria-label="Close">&times;</button>
2397
    <h3 id="rdrrCallDrawerTitle">&mdash;</h3>
2398
    <p class="desc" id="rdrrCallDrawerDesc">&mdash;</p>
2399
    <div id="rdrrCallDrawerBody">
2400
      <div class="loading">
2401
        <div class="spinner"></div>
2402
        Loading&hellip;
2403
      </div>
2404
    </div>
2405
  </div>
2406
</div>
2407
 
37444 ranu 2408
<!-- drill-down panel -->
2409
<div class="rdrr-back" id="rdrrBack">
2410
  <div class="rdrr-panel">
2411
    <button class="x" id="rdrrClose" aria-label="Close">&times;</button>
2412
    <h3 id="rdrrDdTitle">&mdash;</h3>
2413
    <p class="desc" id="rdrrDdDesc">&mdash;</p>
2414
 
2415
    <div class="rdrr-total">
2416
      <div>
2417
        <div class="k">Achieved</div>
2418
        <div class="v" id="rdrrDdAch">&mdash;</div>
2419
      </div>
2420
      <div>
2421
        <div class="k">Target</div>
2422
        <div class="v" id="rdrrDdTgt">&mdash;</div>
2423
      </div>
2424
      <div>
2425
        <div class="k">Achievement</div>
2426
        <div class="v" id="rdrrDdPct">&mdash;</div>
2427
      </div>
2428
    </div>
2429
 
2430
    <div class="sectionlabel" style="margin-top:2px">Movement Split — Achievement vs Target</div>
2431
    <div class="bbars" id="rdrrDdBars"></div>
2432
 
37487 ranu 2433
    <div class="sectionlabel">Warehouse Flow — Buying vs Selling <span
2434
            style="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(GRN into this warehouse + cross-warehouse direct billings)</span>
2435
    </div>
2436
    <div id="rdrrDdFlow">
2437
      <div class="loading">
2438
        <div class="spinner"></div>
2439
        Loading&hellip;
2440
      </div>
2441
    </div>
2442
 
37444 ranu 2443
    <div class="sectionlabel">Brand Mix — Share of Achievement <span
2444
            style="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(click a brand for movement split)</span>
2445
    </div>
2446
    <div id="rdrrDdBrands">
2447
      <div class="loading">
2448
        <div class="spinner"></div>
2449
        Loading brand mix&hellip;
2450
      </div>
2451
    </div>
2452
  </div>
2453
</div>
2454
 
2455
<script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
2456
<script src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
2457
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
2458
<script>
2459
  (function () {
2460
    var DEFAULT_START = "$defaultStartDate";
2461
    var DEFAULT_END = "$defaultEndDate";
2462
 
2463
    // Prefer query-string range if the caller provided one (e.g. from today_po_rbm's date picker).
2464
    var url = new URL(window.location.href);
2465
    var qsStart = url.searchParams.get("startDate");
2466
    var qsEnd = url.searchParams.get("endDate");
2467
    var startDate = qsStart || DEFAULT_START;
2468
    var endDate = qsEnd || DEFAULT_END;
2469
 
2470
    function fmtDate(s) {
2471
      return moment(s).format("DD MMM");
2472
    }
2473
 
2474
    function fmtLakh(v) {
2475
      v = Number(v) || 0;
2476
      return (v / 100000).toFixed(1) + "L";
2477
    }
2478
 
2479
    function pctClass(p) {
2480
      if (p >= 95) return "good";
2481
      if (p >= 41) return "warn";
2482
      return "bad";
2483
    }
2484
 
2485
    function pctLabel(ach, tgt) {
2486
      if (!tgt || tgt <= 0) return {text: "-", cls: "na", num: null};
2487
      var p = (ach / tgt) * 100;
2488
      return {text: p.toFixed(1) + "%", cls: pctClass(p), num: p};
2489
    }
2490
 
2491
    function pctBarWidth(ach, tgt) {
2492
      if (!tgt || tgt <= 0) return 0;
2493
      return Math.min(100, (ach / tgt) * 100);
2494
    }
2495
 
2496
    // Initialise the range picker.
2497
    $("#rdrrPeriod").daterangepicker({
2498
      startDate: moment(startDate),
2499
      endDate: moment(endDate),
2500
      locale: {format: "YYYY-MM-DD", cancelLabel: "Clear"}
2501
    });
2502
    $("#rdrrPeriod").on("apply.daterangepicker", function (ev, picker) {
2503
      startDate = picker.startDate.format("YYYY-MM-DD");
2504
      endDate = picker.endDate.format("YYYY-MM-DD");
2505
    });
2506
 
2507
    // Defensive: read the picker's current state at click time in case
2508
    // the apply.daterangepicker event was swallowed for any reason.
2509
    $("#rdrrApply").on("click", function () {
2510
      var p = $("#rdrrPeriod").data("daterangepicker");
2511
      if (p) {
2512
        startDate = p.startDate.format("YYYY-MM-DD");
2513
        endDate = p.endDate.format("YYYY-MM-DD");
2514
      }
2515
      load();
37517 ranu 2516
      // If a Sales role is currently active, re-fetch sales-data for the new range.
2517
      if (CURRENT_SALES_ROLE) {
37497 ranu 2518
        $.ajax({
2519
          url: "/rbm-drr/sales-data",
2520
          type: "GET",
2521
          data: {startDate: startDate, endDate: endDate},
2522
          dataType: "json",
2523
          success: function (payload) {
2524
            SALES_PAYLOAD = payload;
2525
            renderFiltered();
2526
          },
2527
          error: function () {
2528
            SALES_PAYLOAD = {rows: []};
2529
            renderFiltered();
2530
          }
2531
        });
2532
      }
37444 ranu 2533
    });
2534
 
2535
    function load() {
2536
      $("#rdrrTableWrap").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
37460 ranu 2537
      $("#rdrrAgingWrap").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
2538
      $("#rdrrBillingWrap").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
37444 ranu 2539
      $("#rdrrPeriodLabel").text(fmtDate(startDate) + " – " + fmtDate(endDate));
37460 ranu 2540
      $("#rdrrAgingPeriod").text(fmtDate(startDate) + " – " + fmtDate(endDate));
37444 ranu 2541
      fetch("/rbm-drr/data?startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"})
2542
              .then(function (r) {
2543
                return r.json().then(function (b) {
2544
                  return {ok: r.ok, body: b};
2545
                });
2546
              })
2547
              .then(function (res) {
2548
                if (!res.ok) {
2549
                  $("#rdrrTableWrap").html('<div class="empty">' + escapeHtml(res.body && res.body.error ? res.body.error : "Failed to load.") + '</div>');
2550
                  return;
2551
                }
2552
                render(res.body);
2553
              })
2554
              .catch(function (e) {
2555
                $("#rdrrTableWrap").html('<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>');
2556
              });
37460 ranu 2557
 
2558
      fetch("/rbm-drr/extras?startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"})
2559
              .then(function (r) {
2560
                return r.json().then(function (b) {
2561
                  return {ok: r.ok, body: b};
2562
                });
2563
              })
2564
              .then(function (res) {
2565
                if (!res.ok) {
2566
                  var msg = '<div class="empty">' + escapeHtml(res.body && res.body.error ? res.body.error : "Failed to load.") + '</div>';
2567
                  $("#rdrrAgingWrap").html(msg);
2568
                  $("#rdrrBillingWrap").html(msg);
2569
                  return;
2570
                }
2571
                renderAging(res.body);
2572
                renderPartnerBilling(res.body);
2573
              })
2574
              .catch(function (e) {
2575
                var msg = '<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>';
2576
                $("#rdrrAgingWrap").html(msg);
2577
                $("#rdrrBillingWrap").html(msg);
2578
              });
37444 ranu 2579
    }
2580
 
37460 ranu 2581
    function renderAging(data) {
2582
      var rows = data.aging || [];
2583
      var totals = data.agingTotals || {};
2584
      var balance = data.balance || {};
2585
      if (rows.length === 0) {
2586
        $("#rdrrAgingWrap").html('<div class="empty"><div class="ei">📦</div>No aging inventory sold in this range.</div>');
2587
        return;
2588
      }
2589
      var html = ['<table class="xtable"><thead><tr>',
2590
        '<th>RBM</th>',
2591
        '<th class="num g-hid">HID</th>',
2592
        '<th class="num g-fast">FAST</th>',
2593
        '<th class="num g-run">RUN</th>',
2594
        '<th class="num g-slow">SLOW</th>',
2595
        '<th class="num g-other">OTHER</th>',
2596
        '<th class="num g-total">TOTAL</th>',
2597
        '</tr></thead><tbody>'];
2598
      rows.forEach(function (r) {
2599
        html.push('<tr>' +
2600
                '<td class="rbm-nm">' + escapeHtml(r.rbmName || '-') + '</td>' +
2601
                cell(r.hid, 'g-hid') + cell(r.fast, 'g-fast') + cell(r.run, 'g-run') +
2602
                cell(r.slow, 'g-slow') + cell(r.other, 'g-other') + cell(r.total, 'g-total') +
2603
                '</tr>');
2604
      });
2605
      html.push('<tr class="tot">' +
2606
              '<td>Total</td>' +
2607
              cell(totals.hid, 'g-hid') + cell(totals.fast, 'g-fast') + cell(totals.run, 'g-run') +
2608
              cell(totals.slow, 'g-slow') + cell(totals.other, 'g-other') + cell(totals.total, 'g-total') +
2609
              '</tr>');
2610
      html.push('<tr class="bal">' +
2611
              '<td class="hdr">15-Day Balance Value</td>' +
2612
              cell(balance.hid) + cell(balance.fast) + cell(balance.run) +
2613
              cell(balance.slow) + cell(balance.other) + cell(balance.total) +
2614
              '</tr>');
2615
      html.push('</tbody></table>');
2616
      $("#rdrrAgingWrap").html(html.join(""));
2617
    }
2618
 
2619
    function cell(v, cls) {
2620
      v = Number(v) || 0;
2621
      var zero = v === 0 ? ' zero' : '';
2622
      var extra = cls ? ' ' + cls : '';
2623
      return '<td class="num' + extra + zero + '">' + formatIndian(v) + '</td>';
2624
    }
2625
 
2626
    function formatIndian(n) {
2627
      n = Math.round(Number(n) || 0);
2628
      if (n === 0) return '0';
2629
      var s = n.toString();
2630
      var lastThree = s.substring(s.length - 3);
2631
      var otherNums = s.substring(0, s.length - 3);
2632
      if (otherNums !== '') lastThree = ',' + lastThree;
2633
      return otherNums.replace(/\B(?=(\d{2})+(?!\d))/g, ',') + lastThree;
2634
    }
2635
 
2636
    function renderPartnerBilling(data) {
2637
      var rows = data.partnerBilling || [];
2638
      var totals = data.partnerBillingTotals || {};
2639
      $("#rdrrBillingPeriod").text(data.partnerBillingMonth || '');
2640
      if (rows.length === 0) {
2641
        $("#rdrrBillingWrap").html('<div class="empty"><div class="ei">👥</div>No targeted partners this month.</div>');
2642
        return;
2643
      }
2644
      var html = ['<table class="xtable"><thead><tr>',
2645
        '<th>RBM</th>',
2646
        '<th class="num">Targeted</th>',
2647
        '<th class="num">Week 1<br><small style="color:var(--soft);font-weight:600">(1–7)</small></th>',
2648
        '<th class="num">Week 2<br><small style="color:var(--soft);font-weight:600">(8–15)</small></th>',
2649
        '<th class="num">Week 3<br><small style="color:var(--soft);font-weight:600">(16–22)</small></th>',
2650
        '<th class="num">Week 4<br><small style="color:var(--soft);font-weight:600">(23–end)</small></th>',
2651
        '<th class="num g-total">MTD</th>',
2652
        '<th class="num">Zero Billed<br><small style="color:var(--soft);font-weight:600">(&lt;1L / &lt;20K)</small></th>',
2653
        '</tr></thead><tbody>'];
2654
      rows.forEach(function (r) {
2655
        html.push('<tr>' +
2656
                '<td class="rbm-nm">' + escapeHtml(r.rbmName || '-') + '</td>' +
2657
                '<td class="num">' + pill(r.targeted, '') + '</td>' +
2658
                '<td class="num">' + pill(r.w1, ratio(r.w1, r.targeted)) + '</td>' +
2659
                '<td class="num">' + pill(r.w2, ratio(r.w2, r.targeted)) + '</td>' +
2660
                '<td class="num">' + pill(r.w3, ratio(r.w3, r.targeted)) + '</td>' +
2661
                '<td class="num">' + pill(r.w4, ratio(r.w4, r.targeted)) + '</td>' +
2662
                '<td class="num g-total">' + pill(r.mtd, ratio(r.mtd, r.targeted)) + '</td>' +
2663
                '<td class="num">' + pill(r.zero, r.zero > 0 ? 'warn' : 'zero') + '</td>' +
2664
                '</tr>');
2665
      });
2666
      html.push('<tr class="tot">' +
2667
              '<td>Total</td>' +
2668
              '<td class="num">' + formatIndian(totals.targeted) + '</td>' +
2669
              '<td class="num">' + formatIndian(totals.w1) + '</td>' +
2670
              '<td class="num">' + formatIndian(totals.w2) + '</td>' +
2671
              '<td class="num">' + formatIndian(totals.w3) + '</td>' +
2672
              '<td class="num">' + formatIndian(totals.w4) + '</td>' +
2673
              '<td class="num g-total">' + formatIndian(totals.mtd) + '</td>' +
2674
              '<td class="num">' + formatIndian(totals.zero) + '</td>' +
2675
              '</tr>');
2676
      html.push('</tbody></table>');
2677
      $("#rdrrBillingWrap").html(html.join(""));
2678
    }
2679
 
2680
    function pill(v, cls) {
2681
      v = Number(v) || 0;
2682
      var c = cls || (v === 0 ? 'zero' : '');
2683
      return '<span class="cnt-pill ' + c + '">' + formatIndian(v) + '</span>';
2684
    }
2685
 
2686
    function ratio(part, total) {
2687
      part = Number(part) || 0;
2688
      total = Number(total) || 0;
2689
      if (total === 0) return 'zero';
2690
      var p = (part / total) * 100;
2691
      if (p >= 60) return 'good';
2692
      if (p >= 30) return '';
2693
      if (p > 0) return 'warn';
2694
      return 'zero';
2695
    }
2696
 
37444 ranu 2697
    // Cached latest payload so the "Target Status by RBM" modal can render
2698
    // without a re-fetch when the KPI card is clicked.
2699
    var LAST_PAYLOAD = null;
37517 ranu 2700
    var SALES_PAYLOAD = null;              // fetched from /rbm-drr/sales-data when a Sales role is selected
2701
    var CURRENT_SALES_ROLE = null;         // "ASM" / "CM" / … — when set with SALES_PAYLOAD, we render Sales view
2702
    var CURRENT_SALES_AUTH_ID = null;      // when set, further narrows Sales view to this person
37497 ranu 2703
    // Set of RBM authIds allowed by the current filter. null = no filter (show all).
2704
    var CURRENT_FILTER_AUTH_IDS = null;
37444 ranu 2705
 
2706
    function render(data) {
2707
      LAST_PAYLOAD = data;
37487 ranu 2708
      renderFiltered();
2709
    }
37444 ranu 2710
 
37517 ranu 2711
    // Maps a sales-data row into the same shape the RBM grid renderer expects.
2712
    function normaliseSalesRow(r) {
2713
      return {
2714
        authId: r.salesAuthId, warehouseId: r.warehouseId,
2715
        rbmName: r.salesName + (r.role ? " (" + r.role + ")" : ""),
2716
        warehouseName: r.warehouseName,
2717
        totalAch: r.totalAch, totalTgt: r.totalTgt,
2718
        hidAch: r.hidAch, hidTgt: r.hidTgt,
2719
        fastAch: r.fastAch, fastTgt: r.fastTgt,
2720
        runAch: r.runAch, runTgt: r.runTgt,
2721
        slowAch: r.slowAch, slowTgt: r.slowTgt,
2722
        otherAch: r.otherAch, otherTgt: r.otherTgt
2723
      };
2724
    }
2725
 
37497 ranu 2726
    // Renders KPIs + grid from the appropriate payload:
37517 ranu 2727
    //  - Sales role active + payload loaded → filter SALES_PAYLOAD to that role (and optionally one person)
2728
    //  - RBM filter or no filter → LAST_PAYLOAD filtered by CURRENT_FILTER_AUTH_IDS
37487 ranu 2729
    function renderFiltered() {
37517 ranu 2730
      if (CURRENT_SALES_ROLE && SALES_PAYLOAD) {
37497 ranu 2731
        var salesRows = (SALES_PAYLOAD.rows || []).filter(function (r) {
37517 ranu 2732
          if (r.role !== CURRENT_SALES_ROLE) return false;
2733
          if (CURRENT_SALES_AUTH_ID && String(r.salesAuthId) !== String(CURRENT_SALES_AUTH_ID)) return false;
2734
          return true;
2735
        }).map(normaliseSalesRow);
37497 ranu 2736
        renderRowsAsGrid(salesRows, true);
2737
        return;
2738
      }
37487 ranu 2739
      if (!LAST_PAYLOAD) return;
2740
      var allRows = LAST_PAYLOAD.rows || [];
37497 ranu 2741
      var rows = CURRENT_FILTER_AUTH_IDS
37487 ranu 2742
              ? allRows.filter(function (r) {
37497 ranu 2743
                return CURRENT_FILTER_AUTH_IDS[String(r.authId)];
37487 ranu 2744
              })
2745
              : allRows;
37497 ranu 2746
      renderRowsAsGrid(rows, false);
2747
    }
37487 ranu 2748
 
37497 ranu 2749
    // Extracted from renderFiltered() so both RBM-source and Sales-source paths share it.
2750
    function renderRowsAsGrid(rows, isSalesView) {
2751
 
37487 ranu 2752
      // Recompute totals from the filtered subset. Cheaper + correct vs using server totals.
2753
      var t = {
2754
        totalAch: 0, totalTgt: 0, hidAch: 0, hidTgt: 0, fastAch: 0, fastTgt: 0,
2755
        runAch: 0, runTgt: 0, slowAch: 0, slowTgt: 0, otherAch: 0, otherTgt: 0,
2756
        rbmsAtOrAbove100: 0, rbmRowCount: rows.length
2757
      };
2758
      rows.forEach(function (r) {
2759
        t.totalAch += Number(r.totalAch) || 0;
2760
        t.totalTgt += Number(r.totalTgt) || 0;
2761
        t.hidAch += Number(r.hidAch) || 0;
2762
        t.hidTgt += Number(r.hidTgt) || 0;
2763
        t.fastAch += Number(r.fastAch) || 0;
2764
        t.fastTgt += Number(r.fastTgt) || 0;
2765
        t.runAch += Number(r.runAch) || 0;
2766
        t.runTgt += Number(r.runTgt) || 0;
2767
        t.slowAch += Number(r.slowAch) || 0;
2768
        t.slowTgt += Number(r.slowTgt) || 0;
2769
        t.otherAch += Number(r.otherAch) || 0;
2770
        t.otherTgt += Number(r.otherTgt) || 0;
2771
        if (r.totalTgt > 0 && r.totalAch >= r.totalTgt) t.rbmsAtOrAbove100++;
2772
      });
2773
 
37444 ranu 2774
      $("#rdrrKpiAch").html('&#8377;&nbsp;' + fmtLakh(t.totalAch));
2775
      $("#rdrrKpiTgt").html('&#8377;&nbsp;' + fmtLakh(t.totalTgt));
2776
      var pk = pctLabel(t.totalAch, t.totalTgt);
2777
      $("#rdrrKpiPct").html(pk.cls === "na" ? '<span class="u">n/a</span>' :
2778
              '<span class="pct ' + pk.cls + '">' + pk.text + '</span>');
2779
      $("#rdrrKpiTop").html((t.rbmsAtOrAbove100 || 0) + ' <span class="u">/ ' + (t.rbmRowCount || 0) + '</span>');
2780
 
2781
      if (rows.length === 0) {
37497 ranu 2782
        var emptyMsg;
2783
        if (isSalesView) {
2784
          emptyMsg = 'No Sales target/achievement rows for this person in the range. Has the pushSalesTargets cron run today?';
2785
        } else if (CURRENT_FILTER_AUTH_IDS) {
2786
          emptyMsg = 'No RBM rows in this range for the selected filter.';
2787
        } else {
2788
          emptyMsg = 'No RBM rows in this range.';
2789
        }
2790
        $("#rdrrTableWrap").html('<div class="empty"><div class="ei">📭</div>' + emptyMsg + '</div>');
37444 ranu 2791
        return;
2792
      }
2793
 
2794
      var buckets = [
2795
        {key: "hid", label: "HID"},
2796
        {key: "fast", label: "FAST"},
2797
        {key: "run", label: "RUN"},
2798
        {key: "slow", label: "SLOW"},
2799
        {key: "other", label: "OTHER"}
2800
      ];
2801
 
2802
      var html = ['<table id="rdrrGrid"><thead><tr>',
2803
        '<th>RBM &middot; Warehouse</th>',
2804
        '<th class="num g-total">Total<br><small style="color:var(--soft);font-weight:600">ACH / TGT</small></th>',
2805
        '<th class="num g-total">%</th>'];
2806
      buckets.forEach(function (b) {
2807
        html.push('<th class="num g-' + b.key + '">' + b.label + '<br><small style="color:var(--soft);font-weight:600">ACH / TGT</small></th>');
2808
        html.push('<th class="num g-' + b.key + '">%</th>');
2809
      });
2810
      html.push('<th></th></tr></thead><tbody>');
2811
 
2812
      rows.forEach(function (r) {
2813
        var tr = ['<tr class="clickable" data-authid="' + r.authId + '" data-warehouseid="' + r.warehouseId + '" data-rbmname="' + escapeHtml(r.rbmName) + '" data-warehousename="' + escapeHtml(r.warehouseName) + '">'];
2814
        tr.push('<td><div class="rbm-cell"><span class="rn">' + escapeHtml(r.rbmName) + '</span><span class="wh">' + escapeHtml(r.warehouseName) + '</span></div></td>');
2815
        tr.push(cellPair(r.totalAch, r.totalTgt));
2816
        buckets.forEach(function (b) {
2817
          tr.push(cellPair(r[b.key + "Ach"], r[b.key + "Tgt"]));
2818
        });
2819
        tr.push('<td class="chev">&rsaquo;</td>');
2820
        tr.push('</tr>');
2821
        html.push(tr.join(""));
2822
      });
2823
 
2824
      // totals row
2825
      var totTr = ['<tr class="totalrow"><td><strong>Total</strong></td>'];
2826
      totTr.push(cellPair(t.totalAch, t.totalTgt, true));
2827
      buckets.forEach(function (b) {
2828
        totTr.push(cellPair(t[b.key + "Ach"], t[b.key + "Tgt"], true));
2829
      });
2830
      totTr.push('<td></td></tr>');
2831
      html.push(totTr.join(""));
2832
 
2833
      html.push('</tbody></table>');
2834
      $("#rdrrTableWrap").html(html.join(""));
2835
 
2836
      // Cache the parsed row on each <tr> so drill-down can render bars immediately.
2837
      rows.forEach(function (r) {
2838
        $("#rdrrGrid tbody tr").filter(function () {
2839
          return +this.dataset.authid === r.authId && +this.dataset.warehouseid === r.warehouseId;
2840
        }).each(function () {
2841
          $(this).data("cached", r);
2842
        });
2843
      });
2844
 
2845
      $("#rdrrGrid tbody tr.clickable").on("click", function () {
2846
        var d = this.dataset;
2847
        var cached = $(this).data("cached");
2848
        openDrilldown(+d.authid, +d.warehouseid, d.rbmname, d.warehousename, cached);
2849
      });
2850
    }
2851
 
2852
    function cellPair(ach, tgt, isTotal) {
2853
      var p = pctLabel(ach, tgt);
2854
      var vhtml = '<td class="num"><div class="cell-val">' + fmtLakh(ach) + ' <small>/ ' + fmtLakh(tgt) + '</small></div></td>';
2855
      var barCls = p.cls === "na" ? "" : p.cls;
2856
      var barW = pctBarWidth(ach, tgt);
2857
      var pctHtml = '<td class="num"><span class="pct ' + p.cls + '">' + p.text + '</span>' +
2858
              (barCls ? '<div class="bar"><i class="' + barCls + '" style="width:' + barW.toFixed(1) + '%"></i></div>' : '') +
2859
              '</td>';
2860
      return vhtml + pctHtml;
2861
    }
2862
 
2863
    // --------------------------- drill-down ---------------------------
2864
 
2865
    function openDrilldown(authId, warehouseId, rbmName, warehouseName, cachedRow) {
2866
      var back = $("#rdrrBack");
2867
      $("#rdrrDdTitle").text(rbmName + " — " + warehouseName);
2868
      $("#rdrrDdDesc").text(fmtDate(startDate) + " – " + fmtDate(endDate));
2869
      $("#rdrrDdBars").empty();
2870
      back.addClass("show");
2871
 
2872
      if (cachedRow) {
2873
        renderBars(cachedRow);
2874
      } else {
2875
        $("#rdrrDdAch").text("—");
2876
        $("#rdrrDdTgt").text("—");
2877
        $("#rdrrDdPct").text("—");
2878
      }
2879
      $("#rdrrDdBrands").html('<div class="loading"><div class="spinner"></div>Loading brand mix&hellip;</div>');
37487 ranu 2880
      $("#rdrrDdFlow").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
37444 ranu 2881
 
37533 ranu 2882
      var salesQs = CURRENT_SALES_ROLE ? ("&hierarchy=SALES&role=" + encodeURIComponent(CURRENT_SALES_ROLE)) : "";
37444 ranu 2883
      fetch("/rbm-drr/rbm-detail?authId=" + authId + "&warehouseId=" + warehouseId +
37533 ranu 2884
              "&startDate=" + startDate + "&endDate=" + endDate + salesQs, {credentials: "same-origin"})
37444 ranu 2885
              .then(function (r) {
2886
                return r.json();
2887
              })
2888
              .then(function (data) {
2889
                renderBrands(data);
37487 ranu 2890
                renderWarehouseFlow(data, cachedRow);
37444 ranu 2891
              })
2892
              .catch(function (e) {
37487 ranu 2893
                var msg = '<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>';
2894
                $("#rdrrDdBrands").html(msg);
2895
                $("#rdrrDdFlow").html(msg);
37444 ranu 2896
              });
2897
    }
2898
 
37487 ranu 2899
    function renderWarehouseFlow(data, row) {
2900
      var buying = (data && data.buying) || [];
2901
      // Map buying values by status for O(1) lookup.
2902
      var buyMap = {};
2903
      buying.forEach(function (b) {
2904
        buyMap[b.status] = Number(b.value) || 0;
2905
      });
2906
      var labels = {HID: "HID", FASTMOVING: "FAST", RUNNING: "RUN", SLOWMOVING: "SLOW", OTHER: "OTHER"};
2907
      var order = ["HID", "FASTMOVING", "RUNNING", "SLOWMOVING", "OTHER"];
2908
      // Selling per bucket from the cached grid row (already has ACH per bucket).
2909
      var sellMap = {
2910
        HID: row ? Number(row.hidAch) || 0 : 0,
2911
        FASTMOVING: row ? Number(row.fastAch) || 0 : 0,
2912
        RUNNING: row ? Number(row.runAch) || 0 : 0,
2913
        SLOWMOVING: row ? Number(row.slowAch) || 0 : 0,
2914
        OTHER: row ? Number(row.otherAch) || 0 : 0
2915
      };
2916
      var totBuy = 0, totSell = 0;
2917
      order.forEach(function (s) {
2918
        totBuy += buyMap[s] || 0;
2919
        totSell += sellMap[s] || 0;
2920
      });
2921
 
2922
      // "Coverage" = selling / buying. High coverage = we sold most of what came in.
2923
      // Low coverage = supply piling up (aging risk).
2924
      function covCell(sell, buy) {
2925
        if (!buy || buy <= 0) {
2926
          if (sell > 0) return '<span class="cov-cell na" title="No buying recorded in this range">— <span class="cov-pct" style="color:var(--good)">sold ' + fmtLakh(sell) + '</span></span>';
2927
          return '<span class="cov-cell na">—</span>';
2928
        }
2929
        var pct = (sell / buy) * 100;
2930
        var cls = pct >= 60 ? 'good' : pct >= 25 ? 'warn' : 'bad';
2931
        var w = Math.min(100, pct);
2932
        return '<span class="cov-cell ' + cls + '">' +
2933
                '<span class="cov-bar"><i style="width:' + w.toFixed(1) + '%"></i></span>' +
2934
                '<span class="cov-pct">' + pct.toFixed(1) + '%</span></span>';
2935
      }
2936
 
2937
      var html = '<table class="flow-tbl"><thead><tr>' +
2938
              '<th>Movement</th><th class="num">Buying</th><th class="num">Selling</th><th class="num">Coverage (sold / bought)</th>' +
2939
              '</tr></thead><tbody>';
2940
      order.forEach(function (s) {
2941
        var buy = buyMap[s] || 0, sell = sellMap[s] || 0;
2942
        html += '<tr class="' + s.toLowerCase() + '">' +
2943
                '<td><i></i>' + labels[s] + '</td>' +
2944
                '<td class="num">' + fmtLakh(buy) + '</td>' +
2945
                '<td class="num">' + fmtLakh(sell) + '</td>' +
2946
                '<td class="num">' + covCell(sell, buy) + '</td>' +
2947
                '</tr>';
2948
      });
2949
      html += '<tr class="tot">' +
2950
              '<td>Total</td>' +
2951
              '<td class="num">' + fmtLakh(totBuy) + '</td>' +
2952
              '<td class="num">' + fmtLakh(totSell) + '</td>' +
2953
              '<td class="num">' + covCell(totSell, totBuy) + '</td>' +
2954
              '</tr>';
2955
      html += '</tbody></table>';
2956
      $("#rdrrDdFlow").html(html);
2957
    }
2958
 
37444 ranu 2959
    function renderBrands(data) {
2960
      var brands = (data && data.brands) || [];
2961
      if (brands.length === 0) {
2962
        $("#rdrrDdBrands").html('<div class="empty"><div class="ei">🏷️</div>No brand sales in this range.</div>');
2963
        return;
2964
      }
2965
      var maxShare = brands[0].sharePct || 0.001;
2966
      var html = '<div class="brands">';
2967
      brands.forEach(function (b, idx) {
2968
        var barW = maxShare > 0 ? Math.min(100, (b.sharePct / maxShare) * 100) : 0;
2969
        var topCls = idx === 0 ? ' top' : '';
37460 ranu 2970
        var pc = Number(b.partnerCount) || 0;
2971
        var pcCls = pc === 0 ? ' zero' : '';
2972
        var pcLabel = pc + (pc === 1 ? ' partner' : ' partners');
37444 ranu 2973
        html += '<div class="brand-card' + topCls + '" data-idx="' + idx + '">' +
2974
                '<div class="brand-head">' +
2975
                '<div class="cx">&rsaquo;</div>' +
2976
                '<div class="bn" title="' + escapeHtml(b.brand) + '">' + escapeHtml(b.brand) + '</div>' +
2977
                '<div class="btrack"><i style="width:' + barW.toFixed(1) + '%"></i></div>' +
37460 ranu 2978
                '<div class="bpc' + pcCls + '" title="Distinct partners of this RBM who billed ' + escapeHtml(b.brand) + ' in the range">' +
2979
                '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>' +
2980
                pcLabel +
2981
                '</div>' +
37444 ranu 2982
                '<div class="bval">' + fmtLakh(b.valueSold) + '</div>' +
2983
                '<div class="bshare">' + (b.sharePct || 0).toFixed(1) + '%</div>' +
2984
                '</div>' +
2985
                '<div class="brand-body">' +
2986
                '<div class="bhdr">Movement Split within ' + escapeHtml(b.brand) + '</div>' +
2987
                renderBrandBuckets(b.buckets || []) +
2988
                '</div>' +
2989
                '</div>';
2990
      });
2991
      html += '</div>';
2992
      $("#rdrrDdBrands").html(html);
2993
 
2994
      $("#rdrrDdBrands .brand-head").on("click", function () {
2995
        $(this).closest(".brand-card").toggleClass("open");
2996
      });
2997
    }
2998
 
2999
    function renderBrandBuckets(buckets) {
3000
      if (!buckets || buckets.length === 0) {
3001
        return '<div style="color:var(--soft);font-size:12px;padding:8px 4px">No movement split available.</div>';
3002
      }
3003
      var labels = {HID: "HID", FASTMOVING: "FAST", RUNNING: "RUN", SLOWMOVING: "SLOW", OTHER: "OTHER"};
3004
      var html = '<div class="mbars">';
3005
      buckets.forEach(function (bk) {
3006
        var cls = (bk.status || "OTHER").toLowerCase();
3007
        var label = labels[bk.status] || bk.status || "OTHER";
3008
        var sp = bk.sharePctOfBrand || 0;
3009
        var barW = Math.min(100, sp);
3010
        html += '<div class="mbar ' + cls + '">' +
3011
                '<div class="mname"><i></i>' + label + '</div>' +
3012
                '<div class="mtrack"><i style="width:' + barW.toFixed(1) + '%"></i></div>' +
3013
                '<div class="mval">' + fmtLakh(bk.valueSold) + '</div>' +
3014
                '<div class="mshare">' + sp.toFixed(1) + '%</div>' +
3015
                '</div>';
3016
      });
3017
      html += '</div>';
3018
      return html;
3019
    }
3020
 
3021
    function renderBars(row) {
3022
      var totalAch = row.totalAch, totalTgt = row.totalTgt;
3023
      $("#rdrrDdAch").html('&#8377;&nbsp;' + fmtLakh(totalAch));
3024
      $("#rdrrDdTgt").html('&#8377;&nbsp;' + fmtLakh(totalTgt));
3025
      var pt = pctLabel(totalAch, totalTgt);
3026
      $("#rdrrDdPct").html(pt.cls === "na" ? '<span style="color:var(--soft);font-size:14px">n/a</span>' :
3027
              '<span class="pct ' + pt.cls + '">' + pt.text + '</span>');
3028
 
3029
      var buckets = [
3030
        {key: "hid", label: "HID"},
3031
        {key: "fast", label: "FAST"},
3032
        {key: "run", label: "RUN"},
3033
        {key: "slow", label: "SLOW"},
3034
        {key: "other", label: "OTHER"}
3035
      ];
3036
      var html = "";
3037
      buckets.forEach(function (b) {
3038
        var ach = row[b.key + "Ach"], tgt = row[b.key + "Tgt"];
3039
        var p = pctLabel(ach, tgt);
3040
        var w = pctBarWidth(ach, tgt);
3041
        html += '<div class="bbar ' + b.key + '">' +
3042
                '<div class="bname"><i></i>' + b.label + '</div>' +
3043
                '<div class="btrack"><i style="width:' + w.toFixed(1) + '%"></i></div>' +
3044
                '<div class="btgt"><b>' + fmtLakh(ach) + '</b> / ' + fmtLakh(tgt) + '</div>' +
3045
                '<div class="bpct ' + p.cls + '">' + p.text + '</div>' +
3046
                '</div>';
3047
      });
3048
      $("#rdrrDdBars").html(html);
3049
    }
3050
 
3051
    function escapeHtml(s) {
3052
      if (s == null) return "";
3053
      return String(s).replace(/[&<>"']/g, function (c) {
3054
        return {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}[c];
3055
      });
3056
    }
3057
 
3058
    $("#rdrrClose").on("click", function () {
3059
      $("#rdrrBack").removeClass("show");
3060
    });
3061
    $("#rdrrBack").on("click", function (e) {
3062
      if (e.target === this) $(this).removeClass("show");
3063
    });
3064
 
3065
    // Target-status modal (achieved vs pending) — opens from the KPI card.
3066
    $("#rdrrKpiTopCard").on("click", function () {
3067
      openStatusModal();
3068
    });
3069
    $("#rdrrStatusClose").on("click", function () {
3070
      $("#rdrrStatusBack").removeClass("show");
3071
    });
3072
    $("#rdrrStatusBack").on("click", function (e) {
3073
      if (e.target === this) $(this).removeClass("show");
3074
    });
3075
 
3076
    function openStatusModal() {
3077
      if (!LAST_PAYLOAD) return;
37487 ranu 3078
      // Respect the RBM filter so the modal matches what's on screen.
3079
      var rows = (LAST_PAYLOAD.rows || []).filter(function (r) {
37497 ranu 3080
        return !CURRENT_FILTER_AUTH_IDS || CURRENT_FILTER_AUTH_IDS[String(r.authId)];
37487 ranu 3081
      });
37444 ranu 3082
      var achieved = [], pending = [];
3083
      rows.forEach(function (r) {
3084
        if (!r.totalTgt || r.totalTgt <= 0) return;
3085
        var pct = (r.totalAch / r.totalTgt) * 100;
3086
        var enriched = {row: r, pct: pct};
3087
        if (r.totalAch >= r.totalTgt) achieved.push(enriched);
3088
        else pending.push(enriched);
3089
      });
3090
      achieved.sort(function (a, b) {
3091
        return b.pct - a.pct;
3092
      });
3093
      pending.sort(function (a, b) {
3094
        return a.pct - b.pct;
3095
      });
3096
 
3097
      $("#rdrrStatusDesc").text(fmtDate(startDate) + " – " + fmtDate(endDate));
3098
      $("#rdrrStatusAchCnt").text(achieved.length);
3099
      $("#rdrrStatusPndCnt").text(pending.length);
3100
      $("#rdrrStatusAchList").html(renderStatusList(achieved, "achieved"));
3101
      $("#rdrrStatusPndList").html(renderStatusList(pending, "pending"));
3102
      $("#rdrrStatusBack").addClass("show");
3103
 
3104
      // Bind click on each item to open the same drill-down slide-in.
3105
      $("#rdrrStatusBack .status-item").on("click", function () {
3106
        var d = this.dataset;
3107
        var cachedRow = null;
3108
        (LAST_PAYLOAD.rows || []).some(function (r) {
3109
          if (r.authId == d.authid && r.warehouseId == d.warehouseid) {
3110
            cachedRow = r;
3111
            return true;
3112
          }
3113
          return false;
3114
        });
3115
        $("#rdrrStatusBack").removeClass("show");
3116
        openDrilldown(+d.authid, +d.warehouseid, d.rbmname, d.warehousename, cachedRow);
3117
      });
3118
    }
3119
 
3120
    function renderStatusList(items, kind) {
3121
      if (items.length === 0) {
3122
        return '<div class="status-empty">' +
3123
                (kind === "achieved" ? "No RBM has met target in this range." : "All RBMs met target — nice!") +
3124
                '</div>';
3125
      }
3126
      var html = "";
3127
      items.forEach(function (it) {
3128
        var r = it.row;
3129
        var p = pctClass(it.pct);
3130
        html += '<div class="status-item" title="Click to view drill-down"' +
3131
                ' data-authid="' + r.authId + '" data-warehouseid="' + r.warehouseId + '"' +
3132
                ' data-rbmname="' + escapeHtml(r.rbmName) + '" data-warehousename="' + escapeHtml(r.warehouseName) + '"' +
3133
                ' style="cursor:pointer">' +
3134
                '<div><span class="rn">' + escapeHtml(r.rbmName) + '</span>' +
3135
                '<span class="wh">' + escapeHtml(r.warehouseName) + '</span></div>' +
3136
                '<div class="vv"><b>' + fmtLakh(r.totalAch) + '</b> / ' + fmtLakh(r.totalTgt) + '</div>' +
3137
                '<div class="pp ' + p + '">' + it.pct.toFixed(1) + '%</div>' +
3138
                '</div>';
3139
      });
3140
      return html;
3141
    }
3142
 
37468 ranu 3143
    // ------------------------- Today PO & Billed matrix -------------------------
3144
    // Reuses the existing /indent/today_po_table_fragment endpoint (unchanged).
37522 ranu 3145
    // Parses the returned HTML into a data model and re-renders a compact
3146
    // heatmap grid — one row per person, one column per brand, cell showing
3147
    // Billed (primary) + Partners/PO (secondary), with per-brand heat shading.
37468 ranu 3148
    function loadPurchaseMilestones() {
3149
      var cat = $("#rdrrPmCategory").val() || "RBM";
3150
      $("#rdrrPmWrap").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
3151
      $.ajax({
3152
        url: "/indent/today_po_table_fragment",
3153
        type: "GET",
3154
        data: {categoryType: cat},
3155
        success: function (html) {
37522 ranu 3156
          try {
3157
            var model = parsePurchaseMilestonesFragment(html);
3158
            $("#rdrrPmWrap").html(renderPurchaseMilestonesGrid(model));
3159
          } catch (e) {
3160
            $("#rdrrPmWrap").html('<div class="empty">Failed to render purchase-milestones data.</div>');
3161
          }
37468 ranu 3162
        },
3163
        error: function () {
3164
          $("#rdrrPmWrap").html('<div class="empty">Failed to load purchase-milestones data.</div>');
3165
        }
3166
      });
3167
    }
3168
 
3169
    function numberToIndianCurrency(n) {
3170
      if (n === 0) return "0";
3171
      var s = n.toString();
3172
      var lastThree = s.substring(s.length - 3);
3173
      var other = s.substring(0, s.length - 3);
3174
      if (other !== "") lastThree = "," + lastThree;
3175
      return other.replace(/\B(?=(\d{2})+(?!\d))/g, ",") + lastThree;
3176
    }
3177
 
37570 ranu 3178
    // Compact Lakh format for the Purchase Milestones grid: 623478 → "6.2L", 500 → "0.0L", 15737358 → "157.4L".
3179
    // Always one decimal, always suffixed "L" — matches how other DRR sections display money.
3180
    function pmLakh(n) {
3181
      n = Number(n) || 0;
3182
      return (n / 100000).toFixed(1) + "L";
3183
    }
3184
 
37523 ranu 3185
    // Compact currency: >= 1Cr => "1.19Cr", >= 1L => "18.68L", >= 1K => "28.4K", else raw.
3186
    // `tight=true` drops the space before the unit for narrow cells.
3187
    function pmCompactCurrency(n, tight) {
37522 ranu 3188
      n = Number(n) || 0;
37523 ranu 3189
      var sp = tight ? "" : " ";
3190
      if (n >= 10000000) return "₹" + (n / 10000000).toFixed(2) + sp + "Cr";
3191
      if (n >= 100000) return "₹" + (n / 100000).toFixed(2) + sp + "L";
3192
      if (n >= 1000) return "₹" + (n / 1000).toFixed(1) + sp + "K";
37522 ranu 3193
      return "₹" + numberToIndianCurrency(Math.round(n));
3194
    }
3195
 
3196
    // Parse the injected multi-table fragment into:
3197
    //   { brands: [{name, slug}], rows: [{name, cells: {brand: {po, count, billed}}, totals: {po, count, billed}}] }
3198
    function parsePurchaseMilestonesFragment(html) {
3199
      var doc = new DOMParser().parseFromString(html, "text/html");
3200
      var tables = doc.querySelectorAll("table");
3201
      var brandOrder = [];
3202
      var brandSet = {};
3203
      var rowsMap = {};   // name -> {name, order, cells}
3204
      var rowOrder = [];
3205
 
3206
      tables.forEach(function (tbl) {
3207
        var theadRows = tbl.querySelectorAll("thead tr");
3208
        if (theadRows.length < 1) return;
3209
        // First thead row: [AuthUser th] + brand ths (colspan=3)
3210
        var brandThs = theadRows[0].querySelectorAll("th[colspan]");
3211
        var brands = [];
3212
        brandThs.forEach(function (th) {
3213
          var label = (th.textContent || "").trim().toUpperCase();
3214
          if (!label) return;
3215
          brands.push(label);
3216
          if (!brandSet[label]) {
3217
            brandSet[label] = true;
3218
            brandOrder.push({name: label, slug: label.toLowerCase().replace(/[^a-z0-9]+/g, "")});
3219
          }
3220
        });
3221
 
3222
        tbl.querySelectorAll("tbody tr").forEach(function (tr) {
3223
          var tds = tr.querySelectorAll("td");
3224
          if (tds.length < 1 + brands.length * 3) return;
3225
          var name = (tds[0].textContent || "").trim();
3226
          if (!name) return;
3227
          var row = rowsMap[name];
3228
          if (!row) {
3229
            row = {name: name, cells: {}, isTotal: /^all\b/i.test(name)};
3230
            rowsMap[name] = row;
3231
            rowOrder.push(name);
3232
          }
3233
          for (var i = 0; i < brands.length; i++) {
3234
            var b = brands[i];
3235
            var po = parseFloat((tds[1 + i * 3].textContent || "0").replace(/[^\d.-]/g, "")) || 0;
3236
            var count = parseInt((tds[1 + i * 3 + 1].textContent || "0").replace(/[^\d.-]/g, ""), 10) || 0;
3237
            var billed = parseFloat((tds[1 + i * 3 + 2].textContent || "0").replace(/[^\d.-]/g, "")) || 0;
3238
            row.cells[b] = {po: po, count: count, billed: billed};
3239
          }
3240
        });
3241
      });
3242
 
3243
      // Pull the TOTAL brand out of brandOrder and keep it first (fixed column).
3244
      var totalIdx = brandOrder.findIndex(function (b) {
3245
        return b.name === "TOTAL";
3246
      });
3247
      var totalBrand = totalIdx >= 0 ? brandOrder.splice(totalIdx, 1)[0] : null;
3248
 
3249
      // Sort remaining brands by grand total Billed (biggest first) using the "All …" row if present.
3250
      var totalRowName = rowOrder.find(function (n) {
3251
        return /^all\b/i.test(n);
3252
      });
3253
      var totalRow = totalRowName ? rowsMap[totalRowName] : null;
3254
      if (totalRow) {
3255
        brandOrder.sort(function (a, b) {
3256
          var av = (totalRow.cells[a.name] || {}).billed || 0;
3257
          var bv = (totalRow.cells[b.name] || {}).billed || 0;
3258
          return bv - av;
3259
        });
3260
      }
3261
      if (totalBrand) brandOrder.unshift(totalBrand);
3262
 
3263
      // Order rows: total first, others by grand total Billed (from TOTAL brand or sum).
3264
      var totalRowIdx = totalRowName ? rowOrder.indexOf(totalRowName) : -1;
3265
      if (totalRowIdx > 0) {
3266
        rowOrder.splice(totalRowIdx, 1);
3267
        rowOrder.unshift(totalRowName);
3268
      }
3269
      var head = totalRowIdx >= 0 ? [rowOrder.shift()] : [];
3270
      rowOrder.sort(function (a, b) {
3271
        var av = (rowsMap[a].cells["TOTAL"] || {}).billed || 0;
3272
        var bv = (rowsMap[b].cells["TOTAL"] || {}).billed || 0;
3273
        return bv - av;
3274
      });
3275
      rowOrder = head.concat(rowOrder);
3276
 
3277
      // Per-brand max Billed for heatmap intensity (excludes the totals row).
3278
      var brandMax = {};
3279
      brandOrder.forEach(function (b) {
3280
        var max = 0;
3281
        rowOrder.forEach(function (name) {
3282
          var row = rowsMap[name];
3283
          if (row.isTotal) return;
3284
          var v = (row.cells[b.name] || {}).billed || 0;
3285
          if (v > max) max = v;
3286
        });
3287
        brandMax[b.name] = max;
3288
      });
3289
 
3290
      var rows = rowOrder.map(function (n) {
3291
        return rowsMap[n];
3292
      });
3293
      return {brands: brandOrder, rows: rows, brandMax: brandMax};
3294
    }
3295
 
3296
    function renderPurchaseMilestonesGrid(model) {
3297
      if (!model.rows.length || !model.brands.length) {
3298
        return '<div class="empty">No data.</div>';
3299
      }
3300
      var html = ['<div class="pm-grid-wrap"><table class="pm-grid"><thead><tr>'];
3301
      html.push('<th class="pm-corner">' +
3302
              '<div class="pm-corner-lbl">Person</div>' +
3303
              '<div class="pm-corner-hint">' + model.rows.length + ' · ' + model.brands.length + ' brands</div>' +
3304
              '</th>');
3305
      model.brands.forEach(function (b) {
3306
        html.push('<th class="pm-brand ' + b.slug + '">' + b.name + '</th>');
3307
      });
3308
      html.push('</tr></thead><tbody>');
3309
 
3310
      model.rows.forEach(function (row) {
3311
        html.push('<tr class="pm-row' + (row.isTotal ? ' pm-total-row' : '') + '">');
3312
        html.push('<td class="pm-name">' +
3313
                '<div class="pm-name-txt">' + escapeHtml(row.name) + '</div>' +
3314
                (row.isTotal ? '<div class="pm-name-sub">Grand total</div>' : '') +
3315
                '</td>');
3316
        model.brands.forEach(function (b) {
3317
          var c = row.cells[b.name] || {po: 0, count: 0, billed: 0};
3318
          var max = model.brandMax[b.name] || 0;
3319
          var intensity = (!row.isTotal && max > 0) ? Math.min(1, c.billed / max) : 0;
3320
          var isZero = c.billed === 0 && c.po === 0 && c.count === 0;
37575 ranu 3321
          // Softer blue heatmap: capped alpha keeps dark text readable at max intensity.
3322
          var bgAlpha = intensity > 0 ? (0.05 + intensity * 0.22).toFixed(2) : 0;
37522 ranu 3323
          var style = (!row.isTotal && intensity > 0)
37575 ranu 3324
                  ? ' style="background: rgba(59,130,246,' + bgAlpha + ')"' : '';
37522 ranu 3325
          if (isZero) {
3326
            html.push('<td class="pm-cell pm-cell-zero">&mdash;</td>');
3327
          } else {
37570 ranu 3328
            // Values formatted in Lakh (X.XL) — matches how the drill-down and other DRR sections display money.
3329
            var billedTxt = pmLakh(c.billed);
3330
            var poTxt = pmLakh(c.po);
3331
            var partnerLine = c.count > 0
3332
                    ? ('<div class="pm-partners">' + c.count + '<span class="pm-tag"> partner' + (c.count === 1 ? '' : 's') + '</span></div>')
3333
                    : '';
3334
            var poLine = (c.po > 0 || c.billed > 0)
3335
                    ? ('<div class="pm-po">' + poTxt + '<span class="pm-tag"> PO</span></div>')
3336
                    : '';
3337
            var billedLine = '<div class="pm-billed">' + billedTxt + '</div>';
3338
            // Order: partner (top) → PO (middle) → billed (bottom, primary)
37522 ranu 3339
            html.push('<td class="pm-cell' + (row.isTotal ? ' pm-cell-total' : '') + '"' + style + '>' +
37570 ranu 3340
                    partnerLine + poLine + billedLine +
37522 ranu 3341
                    '</td>');
3342
          }
3343
        });
3344
        html.push('</tr>');
3345
      });
3346
      html.push('</tbody></table></div>');
3347
      return html.join("");
3348
    }
3349
 
3350
    function escapeHtml(s) {
3351
      return String(s == null ? "" : s)
3352
              .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
3353
              .replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3354
    }
3355
 
37468 ranu 3356
    $("#rdrrPmCategory").on("change", loadPurchaseMilestones);
3357
 
3358
    // ------------------------- RBM Performance Dashboard (MTD) -------------------------
3359
    // No dedicated backend endpoint exists — the perf-dashboard table is only rendered
3360
    // inline in /indent/today_po_rbm. We fetch that page and extract just the table.
3361
    // Trade-off: heavier request (server re-runs full page prep). Consider adding a
3362
    // /rbm-drr/performance fragment endpoint later if this becomes a hot path.
3363
    function loadPerformanceDashboard() {
3364
      $("#rdrrPerfWrap").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
3365
      $.ajax({
3366
        url: "/indent/today_po_rbm",
3367
        type: "GET",
3368
        dataType: "html",
3369
        success: function (fullHtml) {
3370
          var doc = new DOMParser().parseFromString(fullHtml, "text/html");
3371
          var tbl = doc.querySelector("#performance-dashboard-table");
3372
          if (!tbl) {
3373
            $("#rdrrPerfWrap").html('<div class="empty"><div class="ei">📊</div>No performance dashboard data available yet.</div>');
3374
            return;
3375
          }
3376
          // Fix any relative image paths (e.g. king.png / trophy-cup.png) to be root-relative.
3377
          tbl.querySelectorAll("img").forEach(function (im) {
3378
            var src = im.getAttribute("src") || "";
3379
            if (src.indexOf("resources/") !== -1 && src.charAt(0) !== "/") {
3380
              im.setAttribute("src", "/" + src.replace(/^\.\.\/+/, ""));
3381
            }
3382
          });
3383
          $("#rdrrPerfWrap").empty().append(tbl);
3384
        },
3385
        error: function () {
3386
          $("#rdrrPerfWrap").html('<div class="empty">Failed to load performance dashboard.</div>');
3387
        }
3388
      });
3389
    }
3390
 
37469 ranu 3391
    // ------------------------- RBM Call Target iframe -------------------------
3392
    // The /indent/rbm_call_target endpoint returns a Velocity FRAGMENT (no <html>,
3393
    // no Bootstrap/jQuery). When loaded directly in an iframe, Bootstrap modals
3394
    // collapse and their inner Loading… contents render inline. Fix: fetch the
3395
    // fragment, wrap it in a self-contained document that loads jQuery + Popper +
3396
    // Bootstrap, then hand it to the iframe via srcdoc. Zero backend change.
3397
    function loadCallTargetIframe() {
3398
      $.ajax({
3399
        url: "/indent/rbm_call_target",
3400
        type: "GET",
3401
        dataType: "html",
3402
        success: function (fragment) {
3403
          var doc = [
3404
            '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">',
3405
            '<meta name="viewport" content="width=device-width,initial-scale=1">',
3406
            '<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">',
3407
            '<style>body{margin:0;padding:16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:#fff}</style>',
3408
            '</head><body>',
3409
            fragment,
3410
            '<script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></' + 'script>',
3411
            '<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></' + 'script>',
3412
            '<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></' + 'script>',
3413
            '</body></html>'
3414
          ].join("");
3415
          var frame = document.getElementById("rdrrCallFrame");
3416
          frame.onload = function () {
3417
            hookCallTargetDrawer(frame);
3418
          };
3419
          frame.srcdoc = doc;
3420
        },
3421
        error: function () {
3422
          document.getElementById("rdrrCallFrame").srcdoc =
3423
                  '<div style="padding:24px;font-family:system-ui;color:#97a1b5">Failed to load call-target view.</div>';
3424
        }
3425
      });
3426
    }
3427
 
3428
    // Redirect the iframe's Bootstrap-modal drill-downs into a slide-in parent drawer
3429
    // that matches the dashboard style. srcdoc iframes inherit the parent origin so
3430
    // we can freely reach into the iframe's window and override its functions.
3431
    function hookCallTargetDrawer(frame) {
3432
      var iw = frame.contentWindow;
3433
      if (!iw) return;
3434
      // Give the source's own <script> a moment to run and define the functions.
3435
      setTimeout(function () {
3436
        try {
3437
          iw.showCalledPartnerDetails = function (authId, rbmName) {
3438
            openCallTargetDrawer("called", authId, rbmName, iw.pageQueryDate);
3439
          };
3440
          iw.showOutOfSequenceDetails = function (authId, rbmName) {
3441
            openCallTargetDrawer("oos", authId, rbmName, iw.pageQueryDate);
3442
          };
3443
        } catch (e) {
3444
          // If cross-origin ever kicks in (shouldn't with srcdoc same-origin),
3445
          // fall back to the iframe's native Bootstrap modal — no crash.
3446
        }
3447
      }, 50);
3448
    }
3449
 
3450
    function openCallTargetDrawer(kind, authId, rbmName, dateStr) {
3451
      var titleWho = rbmName + (dateStr ? " (" + dateStr + ")" : "");
3452
      var title = (kind === "oos" ? "Out of Sequence Calls — " : "Called Partners — ") + titleWho;
3453
      $("#rdrrCallDrawerTitle").text(title);
3454
      $("#rdrrCallDrawerDesc").text(kind === "oos"
3455
              ? "Calls placed outside the recommended sequence."
3456
              : "All calls made by this RBM today, with recordings.");
3457
      $("#rdrrCallDrawerBody").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
3458
      $("#rdrrCallDrawerBack").addClass("show");
3459
 
3460
      var url;
3461
      if (kind === "oos") {
3462
        url = "/indent/rbm_call_target/oos_details?authId=" + encodeURIComponent(authId);
3463
      } else {
3464
        var dp = dateStr ? "&date=" + encodeURIComponent(dateStr) : "";
3465
        url = "/indent/rbm_call_target/called_details?authId=" + encodeURIComponent(authId)
3466
                + "&agentName=" + encodeURIComponent(rbmName) + dp;
3467
      }
3468
      $.ajax({
3469
        url: url, type: "GET", dataType: "json",
3470
        success: function (data) {
3471
          if (!data || data.length === 0) {
3472
            $("#rdrrCallDrawerBody").html('<div class="empty"><div class="ei">📭</div>No entries.</div>');
3473
            return;
3474
          }
3475
          $("#rdrrCallDrawerBody").html(kind === "oos" ? renderOosTable(data) : renderCalledTable(data));
3476
        },
3477
        error: function () {
3478
          $("#rdrrCallDrawerBody").html('<div class="empty">Failed to load details.</div>');
3479
        }
3480
      });
3481
    }
3482
 
3483
    function renderOosTable(data) {
3484
      var html = '<table><thead><tr><th>S.No</th><th>Party Name</th><th>Code</th><th>Time</th></tr></thead><tbody>';
3485
      data.forEach(function (r, i) {
3486
        html += '<tr><td>' + (i + 1) + '</td><td>' + escapeHtml(r.partyName || "-") +
3487
                '</td><td>' + escapeHtml(r.code || "-") + '</td><td>' + escapeHtml(r.time || "-") + '</td></tr>';
3488
      });
3489
      return html + '</tbody></table>';
3490
    }
3491
 
3492
    function renderCalledTable(data) {
3493
      var html = '<table><thead><tr>' +
3494
              '<th>S.No</th><th>Party Name</th><th>Code</th><th>Remark</th>' +
3495
              '<th>Call Status</th><th>Duration</th><th>Date/Time</th><th>Recording</th>' +
3496
              '</tr></thead><tbody>';
3497
      data.forEach(function (r, i) {
3498
        var rowStyle = "";
3499
        var recording = "-";
3500
        if (r.breakLog) {
3501
          var status = (r.breakStatus || "").toLowerCase();
3502
          if (status.indexOf("available") >= 0) rowStyle = 'style="background:#fdecec;color:#721c24"';
3503
          else if (status.indexOf("trying") >= 0) rowStyle = 'style="background:#fff5d6;color:#856404"';
3504
          else if (status.indexOf("logged out") >= 0 || status.indexOf("logout") >= 0) rowStyle = 'style="background:#eef0f4;color:#383d41"';
3505
          else if (status.indexOf("break") >= 0) rowStyle = 'style="background:#ffe5cc;color:#c45200"';
3506
        }
3507
        if (r.recordingUrl) {
3508
          recording = '<audio controls preload="none" src="' + escapeHtml(r.recordingUrl) + '"></audio>';
3509
        }
3510
        html += '<tr ' + rowStyle + '>' +
3511
                '<td>' + (i + 1) + '</td>' +
3512
                '<td>' + escapeHtml(r.partyName || (r.breakLog ? (r.breakStatus || "-") : "-")) + '</td>' +
3513
                '<td>' + escapeHtml(r.code || "-") + '</td>' +
3514
                '<td>' + escapeHtml(r.remark || "-") + '</td>' +
3515
                '<td>' + escapeHtml(r.callStatus || "-") + '</td>' +
3516
                '<td>' + escapeHtml(r.callDuration || "-") + '</td>' +
3517
                '<td>' + escapeHtml(r.callDateTime || "-") + '</td>' +
3518
                '<td>' + recording + '</td>' +
3519
                '</tr>';
3520
      });
3521
      return html + '</tbody></table>';
3522
    }
3523
 
3524
    $("#rdrrCallDrawerClose").on("click", function () {
3525
      $("#rdrrCallDrawerBack").removeClass("show");
3526
    });
3527
    $("#rdrrCallDrawerBack").on("click", function (e) {
3528
      if (e.target === this) $(this).removeClass("show");
3529
    });
3530
 
37497 ranu 3531
    // ------------------------- cascading filter (Type → [Level] → Person) -------------------------
3532
    // Flow:
3533
    //   Type = RBM   → Person dropdown = all RBMs. Pick one to filter to that RBM.
3534
    //   Type = SALES → Level dropdown appears (ASM/CM/DM/BM/RSM/NSM). Pick a level
3535
    //                  → Person dropdown = active people at that level. Pick a person
3536
    //                  → fetches /rbm-drr/sales-data and shows that person's aggregated rows.
3537
    // One backend call caches both the RBM list and the Sales list.
3538
    var ALL_RBMS = [];         // [{authId, name}, ...]
3539
    var ALL_SALES_BY_ROLE = {}; // {ASM: [...], CM: [...], ...}
3540
 
3541
    function loadFilterOptions() {
3542
      $.ajax({
3543
        url: "/rbm-drr/all-sales", type: "GET", dataType: "json",
3544
        success: function (payload) {
3545
          ALL_RBMS = (payload && payload.rbms) || [];
3546
          ALL_SALES_BY_ROLE = {};
3547
          ((payload && payload.sales) || []).forEach(function (s) {
3548
            (ALL_SALES_BY_ROLE[s.role] = ALL_SALES_BY_ROLE[s.role] || []).push(s);
3549
          });
3550
          repopulatePersonForCurrentType();
3551
        }
3552
      });
3553
    }
3554
 
3555
    // Refill the Person dropdown based on current Type (+ Level if Sales).
3556
    function repopulatePersonForCurrentType() {
3557
      var type = $("#rdrrTypeSelect").val();
3558
      var personSel = $("#rdrrPersonSelect");
3559
      if (type === "RBM") {
3560
        var html = '<option value="">All RBMs (' + ALL_RBMS.length + ')</option>';
3561
        ALL_RBMS.forEach(function (r) {
3562
          html += '<option value="' + r.authId + '">' + escapeHtml(r.name || "Unknown") + '</option>';
3563
        });
3564
        personSel.html(html).prop("disabled", false);
3565
        return;
3566
      }
3567
      // SALES
3568
      var level = $("#rdrrLevelSelect").val();
3569
      if (!level) {
3570
        personSel.html('<option value="">Pick a level first</option>').prop("disabled", true);
3571
        return;
3572
      }
3573
      var list = ALL_SALES_BY_ROLE[level] || [];
3574
      var salesHtml = '<option value="">All ' + escapeHtml(level) + 's (' + list.length + ')</option>';
3575
      list.forEach(function (s) {
3576
        salesHtml += '<option value="' + s.authId + '">' + escapeHtml(s.name || "Unknown") + '</option>';
3577
      });
3578
      personSel.html(salesHtml).prop("disabled", false);
3579
    }
3580
 
37517 ranu 3581
    // Fetches (or refetches) /rbm-drr/sales-data for the current date range,
3582
    // then renders. Called on Level change and on Apply while Sales view active.
3583
    function fetchAndRenderSales() {
3584
      $("#rdrrTableWrap").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
3585
      $.ajax({
3586
        url: "/rbm-drr/sales-data", type: "GET",
3587
        data: {startDate: startDate, endDate: endDate}, dataType: "json",
3588
        success: function (payload) {
3589
          SALES_PAYLOAD = payload;
3590
          renderFiltered();
3591
        },
3592
        error: function () {
3593
          SALES_PAYLOAD = {rows: []};
3594
          renderFiltered();
3595
        }
3596
      });
3597
    }
3598
 
37497 ranu 3599
    // Type toggles: show/hide Level; reset filter state; refresh Person options.
3600
    $("#rdrrTypeSelect").on("change", function () {
3601
      var type = $(this).val();
3602
      if (type === "SALES") {
3603
        $("#rdrrLevelSelect").css("display", "").val("");
3604
      } else {
3605
        $("#rdrrLevelSelect").css("display", "none").val("");
3606
      }
3607
      // Clear all filter state whenever the type changes.
3608
      CURRENT_FILTER_AUTH_IDS = null;
37517 ranu 3609
      CURRENT_SALES_ROLE = null;
37497 ranu 3610
      CURRENT_SALES_AUTH_ID = null;
3611
      SALES_PAYLOAD = null;
3612
      repopulatePersonForCurrentType();
37487 ranu 3613
      renderFiltered();
3614
    });
3615
 
37497 ranu 3616
    $("#rdrrLevelSelect").on("change", function () {
37517 ranu 3617
      var level = $(this).val();
3618
      // Reset the person-specific filter; role determines the view.
37497 ranu 3619
      CURRENT_FILTER_AUTH_IDS = null;
3620
      CURRENT_SALES_AUTH_ID = null;
37517 ranu 3621
      CURRENT_SALES_ROLE = level || null;
37497 ranu 3622
      repopulatePersonForCurrentType();
37517 ranu 3623
      if (level) {
3624
        // Fetch Sales-data once, then all subsequent role/person filtering is client-side.
3625
        fetchAndRenderSales();
3626
      } else {
3627
        SALES_PAYLOAD = null;
3628
        renderFiltered();
3629
      }
37497 ranu 3630
    });
3631
 
3632
    $("#rdrrPersonSelect").on("change", function () {
3633
      var authId = $(this).val();
3634
      var type = $("#rdrrTypeSelect").val();
3635
 
3636
      if (type === "RBM") {
3637
        // RBM filter (single authId) — no server round trip needed.
37517 ranu 3638
        CURRENT_SALES_ROLE = null;
37497 ranu 3639
        CURRENT_SALES_AUTH_ID = null;
3640
        SALES_PAYLOAD = null;
3641
        if (!authId) {
3642
          CURRENT_FILTER_AUTH_IDS = null;
3643
        } else {
3644
          var single = {};
3645
          single[String(authId)] = true;
3646
          CURRENT_FILTER_AUTH_IDS = single;
3647
        }
3648
        renderFiltered();
3649
        return;
3650
      }
3651
 
37517 ranu 3652
      // SALES type — role already active from Level dropdown; SALES_PAYLOAD already fetched.
3653
      // Person selection just further filters client-side (no re-fetch).
37497 ranu 3654
      CURRENT_FILTER_AUTH_IDS = null;
37517 ranu 3655
      CURRENT_SALES_AUTH_ID = authId || null;
3656
      if (SALES_PAYLOAD) {
37497 ranu 3657
        renderFiltered();
37517 ranu 3658
      } else if (CURRENT_SALES_ROLE) {
3659
        // Edge case: person changed before Level's fetch completed — trigger the fetch.
3660
        fetchAndRenderSales();
37497 ranu 3661
      }
3662
    });
3663
 
37444 ranu 3664
    // initial load
37497 ranu 3665
    loadFilterOptions();
37444 ranu 3666
    load();
37468 ranu 3667
    loadPurchaseMilestones();
3668
    loadPerformanceDashboard();
37469 ranu 3669
    loadCallTargetIframe();
37444 ranu 3670
  })();
3671
</script>
3672
</body>
3673
</html>