Subversion Repositories SmartDukaan

Rev

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

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