Subversion Repositories SmartDukaan

Rev

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