Subversion Repositories SmartDukaan

Rev

Rev 37460 | Go to most recent revision | Details | 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 {
54
      max-width: 1440px;
55
      margin: 0 auto;
56
      padding: 22px 26px 100px
57
    }
58
 
59
    a {
60
      color: var(--brand);
61
      text-decoration: none
62
    }
63
 
64
    a:hover {
65
      text-decoration: underline
66
    }
67
 
68
    /* header */
69
    header.top {
70
      display: flex;
71
      align-items: flex-end;
72
      justify-content: space-between;
73
      gap: 18px;
74
      flex-wrap: wrap;
75
      margin-bottom: 20px
76
    }
77
 
78
    .brand .crumb {
79
      font-size: 12px;
80
      color: var(--soft);
81
      font-weight: 600
82
    }
83
 
84
    .brand h1 {
85
      margin: 4px 0 0;
86
      font-size: 26px;
87
      font-weight: 770;
88
      letter-spacing: -.015em;
89
      display: flex;
90
      align-items: center;
91
      gap: 12px
92
    }
93
 
94
    .brand h1 .live {
95
      font-size: 11px;
96
      font-weight: 700;
97
      color: var(--teal);
98
      background: #e6fcf5;
99
      border: 1px solid #c3fae8;
100
      border-radius: 999px;
101
      padding: 3px 10px
102
    }
103
 
104
    .brand h1 .live i {
105
      display: inline-block;
106
      width: 7px;
107
      height: 7px;
108
      border-radius: 50%;
109
      background: var(--teal);
110
      margin-right: 5px;
111
      animation: pulse 1.6s infinite;
112
      vertical-align: middle
113
    }
114
 
115
    @keyframes pulse {
116
      0%, 100% {
117
        opacity: 1
118
      }
119
      50% {
120
        opacity: .25
121
      }
122
    }
123
 
124
    .hd-right {
125
      display: flex;
126
      align-items: center;
127
      gap: 10px;
128
      flex-wrap: wrap
129
    }
130
 
131
    .ctl {
132
      background: var(--card);
133
      border: 1px solid var(--line);
134
      border-radius: 10px;
135
      padding: 9px 13px;
136
      font-size: 13px;
137
      color: var(--ink);
138
      font-weight: 600;
139
      box-shadow: 0 1px 2px rgba(20, 30, 60, .04)
140
    }
141
 
142
    input.ctl {
143
      appearance: none;
144
      cursor: pointer;
145
      height: 38px;
146
      min-width: 240px
147
    }
148
 
149
    .btn-r {
150
      background: var(--brand);
151
      border: 0;
152
      color: #fff;
153
      font-weight: 670;
154
      padding: 9px 18px;
155
      border-radius: 10px;
156
      cursor: pointer;
157
      font-size: 13px;
158
      height: 38px;
159
      box-shadow: 0 1px 2px rgba(59, 91, 219, .35);
160
      transition: transform .1s, box-shadow .1s
161
    }
162
 
163
    .btn-r:hover {
164
      background: var(--brand2);
165
      box-shadow: 0 3px 10px rgba(59, 91, 219, .35)
166
    }
167
 
168
    .btn-r:active {
169
      transform: translateY(1px)
170
    }
171
 
172
    /* KPI cards */
173
    .kpis {
174
      display: grid;
175
      grid-template-columns:repeat(4, 1fr);
176
      gap: 14px;
177
      margin-bottom: 22px
178
    }
179
 
180
    .kpi {
181
      position: relative;
182
      background: var(--card);
183
      border: 1px solid var(--line);
184
      border-radius: 14px;
185
      padding: 16px 18px;
186
      box-shadow: 0 1px 2px rgba(20, 30, 60, .05);
187
      display: flex;
188
      flex-direction: column;
189
      min-height: 118px;
190
      overflow: hidden
191
    }
192
 
193
    .kpi::before {
194
      content: "";
195
      position: absolute;
196
      top: 0;
197
      left: 0;
198
      height: 3px;
199
      width: 100%;
200
      border-top-left-radius: 14px;
201
      border-top-right-radius: 14px
202
    }
203
 
204
    .kpi.a::before {
205
      background: linear-gradient(90deg, var(--brand), var(--brand2))
206
    }
207
 
208
    .kpi.b::before {
209
      background: linear-gradient(90deg, var(--teal), #20c997)
210
    }
211
 
212
    .kpi.c::before {
213
      background: linear-gradient(90deg, var(--amber), #ffc078)
214
    }
215
 
216
    .kpi.d::before {
217
      background: linear-gradient(90deg, var(--violet), #9775fa)
218
    }
219
 
220
    .kpi .top {
221
      display: flex;
222
      align-items: center;
223
      gap: 10px
224
    }
225
 
226
    .kpi .ic {
227
      width: 34px;
228
      height: 34px;
229
      border-radius: 10px;
230
      display: flex;
231
      align-items: center;
232
      justify-content: center;
233
      flex: none;
234
      color: #fff;
235
      box-shadow: 0 2px 6px rgba(0, 0, 0, .08)
236
    }
237
 
238
    .kpi.a .ic {
239
      background: linear-gradient(135deg, var(--brand), var(--brand2))
240
    }
241
 
242
    .kpi.b .ic {
243
      background: linear-gradient(135deg, var(--teal), #20c997)
244
    }
245
 
246
    .kpi.c .ic {
247
      background: linear-gradient(135deg, var(--amber), #ffc078);
248
      color: #3a2500
249
    }
250
 
251
    .kpi.d .ic {
252
      background: linear-gradient(135deg, var(--violet), #9775fa)
253
    }
254
 
255
    .kpi .ic svg {
256
      width: 18px;
257
      height: 18px
258
    }
259
 
260
    .kpi .label {
261
      font-size: 12px;
262
      color: var(--muted);
263
      font-weight: 650;
264
      line-height: 1.2;
265
      letter-spacing: .005em
266
    }
267
 
268
    .kpi .val {
269
      font-size: 28px;
270
      font-weight: 780;
271
      margin-top: auto;
272
      padding-top: 10px;
273
      letter-spacing: -.02em;
274
      line-height: 1;
275
      font-variant-numeric: tabular-nums
276
    }
277
 
278
    .kpi .val .u {
279
      font-size: 13px;
280
      color: var(--soft);
281
      font-weight: 600;
282
      margin-left: 3px
283
    }
284
 
285
    .kpi .foot {
286
      font-size: 11.5px;
287
      color: var(--soft);
288
      margin-top: 8px;
289
      font-weight: 500
290
    }
291
 
292
    .kpi .val .pct {
293
      margin-left: 0;
294
      font-size: 20px;
295
      padding: 4px 12px
296
    }
297
 
298
    .kpi.clickable {
299
      cursor: pointer;
300
      transition: transform .1s, box-shadow .12s, border-color .12s
301
    }
302
 
303
    .kpi.clickable:hover {
304
      transform: translateY(-2px);
305
      box-shadow: 0 6px 18px rgba(20, 30, 60, .08);
306
      border-color: #c8d2f4
307
    }
308
 
309
    .kpi.clickable .hint {
310
      position: absolute;
311
      bottom: 8px;
312
      right: 12px;
313
      color: var(--soft);
314
      font-size: 11px;
315
      font-weight: 600;
316
      opacity: .7
317
    }
318
 
319
    .kpi.clickable:hover .hint {
320
      opacity: 1;
321
      color: var(--brand)
322
    }
323
 
324
    /* Cards & section labels */
325
    .card {
326
      background: var(--card);
327
      border: 1px solid var(--line);
328
      border-radius: 15px;
329
      padding: 18px 20px;
330
      margin-bottom: 16px;
331
      box-shadow: 0 1px 2px rgba(20, 30, 60, .04)
332
    }
333
 
334
    .card .card-hd {
335
      display: flex;
336
      align-items: baseline;
337
      justify-content: space-between;
338
      gap: 14px;
339
      margin-bottom: 14px
340
    }
341
 
342
    .card h2 {
343
      margin: 0;
344
      font-size: 16px;
345
      font-weight: 730;
346
      letter-spacing: -.01em
347
    }
348
 
349
    .card .desc {
350
      color: var(--muted);
351
      font-size: 12.5px;
352
      margin: 2px 0 0
353
    }
354
 
355
    .card .badge-period {
356
      background: #eef2ff;
357
      border: 1px solid #d7defb;
358
      color: var(--brand);
359
      border-radius: 999px;
360
      padding: 3px 12px;
361
      font-size: 12px;
362
      font-weight: 650;
363
      letter-spacing: .01em
364
    }
365
 
366
    .sectionlabel {
367
      font-size: 11px;
368
      letter-spacing: .14em;
369
      text-transform: uppercase;
370
      color: var(--soft);
371
      font-weight: 700;
372
      margin: 20px 2px 10px
373
    }
374
 
375
    /* Legend under section label */
376
    .legend {
377
      display: flex;
378
      gap: 14px;
379
      flex-wrap: wrap;
380
      margin: 0 2px 10px;
381
      align-items: center;
382
      color: var(--soft);
383
      font-size: 11.5px;
384
      font-weight: 600
385
    }
386
 
387
    .legend .lg {
388
      display: inline-flex;
389
      align-items: center;
390
      gap: 5px
391
    }
392
 
393
    .legend .lg i {
394
      width: 8px;
395
      height: 8px;
396
      border-radius: 50%;
397
      display: inline-block
398
    }
399
 
400
    .legend .lg.hid i {
401
      background: var(--hid)
402
    }
403
 
404
    .legend .lg.fast i {
405
      background: var(--fast)
406
    }
407
 
408
    .legend .lg.run i {
409
      background: var(--run)
410
    }
411
 
412
    .legend .lg.slow i {
413
      background: var(--slow)
414
    }
415
 
416
    .legend .lg.other i {
417
      background: var(--other)
418
    }
419
 
420
    .legend .lg .lgt {
421
      color: var(--muted)
422
    }
423
 
424
    /* Table */
425
    table {
426
      width: 100%;
427
      border-collapse: separate;
428
      border-spacing: 0;
429
      font-size: 13px
430
    }
431
 
432
    th, td {
433
      text-align: left;
434
      padding: 11px 10px;
435
      border-bottom: 1px solid var(--line2)
436
    }
437
 
438
    thead th {
439
      font-size: 10.5px;
440
      text-transform: uppercase;
441
      letter-spacing: .06em;
442
      color: var(--soft);
443
      font-weight: 700;
444
      white-space: nowrap;
445
      user-select: none;
446
      position: sticky;
447
      top: 0;
448
      background: #fff;
449
      z-index: 1;
450
      border-bottom: 1px solid var(--line)
451
    }
452
 
453
    th.num, td.num {
454
      text-align: right
455
    }
456
 
457
    th.g-hid {
458
      color: var(--hid);
459
      border-top: 3px solid var(--hid)
460
    }
461
 
462
    th.g-fast {
463
      color: var(--fast);
464
      border-top: 3px solid var(--fast)
465
    }
466
 
467
    th.g-run {
468
      color: var(--run);
469
      border-top: 3px solid var(--run)
470
    }
471
 
472
    th.g-slow {
473
      color: var(--slow);
474
      border-top: 3px solid var(--slow)
475
    }
476
 
477
    th.g-other {
478
      color: var(--other);
479
      border-top: 3px solid var(--other)
480
    }
481
 
482
    th.g-total {
483
      color: var(--brand);
484
      border-top: 3px solid var(--brand)
485
    }
486
 
487
    tbody tr:hover {
488
      background: #f6f8fd
489
    }
490
 
491
    tbody tr.clickable {
492
      cursor: pointer;
493
      transition: background .1s
494
    }
495
 
496
    tbody tr.clickable:hover {
497
      background: #eef2ff
498
    }
499
 
500
    tr.totalrow {
501
      background: #fafbfe;
502
      border-top: 2px solid var(--line)
503
    }
504
 
505
    tr.totalrow td {
506
      font-weight: 770;
507
      color: var(--ink)
508
    }
509
 
510
    tr.totalrow .cell-val {
511
      font-size: 14px
512
    }
513
 
514
    .rbm-cell {
515
      display: flex;
516
      flex-direction: column;
517
      gap: 2px
518
    }
519
 
520
    .rbm-cell .rn {
521
      font-weight: 700;
522
      color: var(--ink);
523
      font-size: 13.5px
524
    }
525
 
526
    .rbm-cell .wh {
527
      font-size: 11.5px;
528
      color: var(--muted);
529
      font-weight: 600
530
    }
531
 
532
    .cell-val {
533
      font-variant-numeric: tabular-nums;
534
      font-weight: 650;
535
      color: var(--ink);
536
      white-space: nowrap
537
    }
538
 
539
    .cell-val small {
540
      color: var(--soft);
541
      font-weight: 500;
542
      margin-left: 3px
543
    }
544
 
545
    .pct {
546
      display: inline-flex;
547
      align-items: center;
548
      justify-content: center;
549
      min-width: 56px;
550
      padding: 3px 9px;
551
      border-radius: 7px;
552
      font-size: 11.5px;
553
      font-weight: 700;
554
      text-align: center;
555
      font-variant-numeric: tabular-nums;
556
      line-height: 1.35
557
    }
558
 
559
    .pct.good {
560
      background: #e5f7ee;
561
      color: #177a3a
562
    }
563
 
564
    .pct.warn {
565
      background: #fff0e0;
566
      color: #a04600
567
    }
568
 
569
    .pct.bad {
570
      background: #ffeaea;
571
      color: #b8272b
572
    }
573
 
574
    .pct.na {
575
      background: transparent;
576
      color: var(--soft);
577
      font-weight: 600;
578
      min-width: auto;
579
      padding: 0
580
    }
581
 
582
    .bar {
583
      height: 5px;
584
      border-radius: 3px;
585
      background: #eef1f6;
586
      overflow: hidden;
587
      margin-top: 6px
588
    }
589
 
590
    .bar > i {
591
      display: block;
592
      height: 100%;
593
      border-radius: 3px;
594
      transition: width .3s ease
595
    }
596
 
597
    .bar > i.good {
598
      background: linear-gradient(90deg, #51cf66, #2f9e44)
599
    }
600
 
601
    .bar > i.warn {
602
      background: linear-gradient(90deg, #ffa94d, #e8590c)
603
    }
604
 
605
    .bar > i.bad {
606
      background: linear-gradient(90deg, #ff8787, #e03131)
607
    }
608
 
609
    .chev {
610
      color: #c5cce0;
611
      font-size: 16px;
612
      padding-left: 6px;
613
      transition: transform .12s, color .12s
614
    }
615
 
616
    tr.clickable:hover .chev {
617
      color: var(--brand);
618
      transform: translateX(2px)
619
    }
620
 
621
    .loading {
622
      padding: 70px;
623
      text-align: center;
624
      color: var(--muted)
625
    }
626
 
627
    .spinner {
628
      width: 32px;
629
      height: 32px;
630
      border: 3px solid #e3e8f5;
631
      border-top-color: var(--brand);
632
      border-radius: 50%;
633
      animation: spin 1s linear infinite;
634
      margin: 0 auto 12px
635
    }
636
 
637
    @keyframes spin {
638
      to {
639
        transform: rotate(360deg)
640
      }
641
    }
642
 
643
    .empty {
644
      padding: 56px 20px;
645
      text-align: center;
646
      color: var(--soft);
647
      font-size: 13.5px
648
    }
649
 
650
    .empty .ei {
651
      font-size: 36px;
652
      margin-bottom: 8px;
653
      opacity: .5
654
    }
655
 
656
    .foot-note {
657
      color: var(--soft);
658
      font-size: 11.5px;
659
      margin-top: 14px;
660
      padding: 10px 12px;
661
      background: #fafbfe;
662
      border: 1px dashed var(--line);
663
      border-radius: 10px;
664
      line-height: 1.5
665
    }
666
 
667
    .foot-note code {
668
      background: #eef1f6;
669
      color: var(--muted);
670
      padding: 1px 5px;
671
      border-radius: 4px;
672
      font-size: 11px
673
    }
674
 
675
    /* drill-down panel */
676
    .rdrr-back {
677
      position: fixed;
678
      inset: 0;
679
      background: rgba(15, 22, 40, .5);
680
      backdrop-filter: blur(2px);
681
      z-index: 1200;
682
      display: none
683
    }
684
 
685
    .rdrr-back.show {
686
      display: block
687
    }
688
 
689
    .rdrr-panel {
690
      position: absolute;
691
      top: 0;
692
      right: 0;
693
      height: 100%;
694
      width: min(760px, 96vw);
695
      background: #fff;
696
      box-shadow: -12px 0 40px rgba(20, 30, 60, .22);
697
      display: flex;
698
      flex-direction: column;
699
      padding: 24px 26px 20px;
700
      transform: translateX(100%);
701
      transition: transform .22s ease;
702
      overflow-y: auto
703
    }
704
 
705
    .rdrr-back.show .rdrr-panel {
706
      transform: translateX(0)
707
    }
708
 
709
    .rdrr-panel .x {
710
      position: absolute;
711
      top: 16px;
712
      right: 18px;
713
      cursor: pointer;
714
      color: var(--soft);
715
      font-size: 24px;
716
      line-height: 1;
717
      border: 0;
718
      background: none;
719
      width: 32px;
720
      height: 32px;
721
      border-radius: 8px;
722
      transition: background .1s, color .1s
723
    }
724
 
725
    .rdrr-panel .x:hover {
726
      background: #f1f3f9;
727
      color: var(--ink)
728
    }
729
 
730
    .rdrr-panel h3 {
731
      margin: 0 0 3px;
732
      font-size: 19px;
733
      font-weight: 750;
734
      letter-spacing: -.01em;
735
      padding-right: 40px
736
    }
737
 
738
    .rdrr-panel > .desc {
739
      margin: 0 0 18px;
740
      font-size: 12.5px;
741
      color: var(--muted);
742
      font-weight: 600
743
    }
744
 
745
    .rdrr-total {
746
      display: flex;
747
      gap: 14px;
748
      padding: 14px 16px;
749
      background: linear-gradient(135deg, #f8f9fc, #eef2ff);
750
      border: 1px solid #dbe2f6;
751
      border-radius: 12px;
752
      margin-bottom: 18px
753
    }
754
 
755
    .rdrr-total > div {
756
      flex: 1;
757
      text-align: center;
758
      border-right: 1px solid #dbe2f6;
759
      padding: 0 4px
760
    }
761
 
762
    .rdrr-total > div:last-child {
763
      border-right: 0
764
    }
765
 
766
    .rdrr-total .k {
767
      font-size: 10.5px;
768
      color: var(--muted);
769
      text-transform: uppercase;
770
      letter-spacing: .06em;
771
      font-weight: 700
772
    }
773
 
774
    .rdrr-total .v {
775
      font-size: 20px;
776
      font-weight: 770;
777
      color: var(--ink);
778
      font-variant-numeric: tabular-nums;
779
      margin-top: 5px;
780
      letter-spacing: -.01em
781
    }
782
 
783
    .rdrr-total .v .pct {
784
      font-size: 16px;
785
      padding: 3px 10px;
786
      margin-top: 0
787
    }
788
 
789
    .bbars {
790
      display: flex;
791
      flex-direction: column;
792
      gap: 10px;
793
      margin-bottom: 20px
794
    }
795
 
796
    .bbar {
797
      display: grid;
798
      grid-template-columns:82px 1fr 145px 60px;
799
      gap: 14px;
800
      align-items: center;
801
      padding: 12px 14px;
802
      background: #fff;
803
      border: 1px solid var(--line);
804
      border-radius: 11px;
805
      transition: box-shadow .12s
806
    }
807
 
808
    .bbar:hover {
809
      box-shadow: 0 3px 10px rgba(20, 30, 60, .06)
810
    }
811
 
812
    .bbar .bname {
813
      font-size: 11.5px;
814
      font-weight: 750;
815
      color: var(--ink);
816
      letter-spacing: .04em;
817
      text-transform: uppercase;
818
      display: flex;
819
      align-items: center;
820
      gap: 6px
821
    }
822
 
823
    .bbar .bname i {
824
      display: inline-block;
825
      width: 8px;
826
      height: 8px;
827
      border-radius: 50%
828
    }
829
 
830
    .bbar.hid .bname {
831
      color: var(--hid)
832
    }
833
 
834
    .bbar.fast .bname {
835
      color: var(--fast)
836
    }
837
 
838
    .bbar.run .bname {
839
      color: var(--run)
840
    }
841
 
842
    .bbar.slow .bname {
843
      color: var(--slow)
844
    }
845
 
846
    .bbar.other .bname {
847
      color: var(--other)
848
    }
849
 
850
    .bbar.hid .bname i {
851
      background: var(--hid)
852
    }
853
 
854
    .bbar.fast .bname i {
855
      background: var(--fast)
856
    }
857
 
858
    .bbar.run .bname i {
859
      background: var(--run)
860
    }
861
 
862
    .bbar.slow .bname i {
863
      background: var(--slow)
864
    }
865
 
866
    .bbar.other .bname i {
867
      background: var(--other)
868
    }
869
 
870
    .bbar .btrack {
871
      height: 14px;
872
      border-radius: 8px;
873
      overflow: hidden;
874
      position: relative
875
    }
876
 
877
    .bbar.hid .btrack {
878
      background: var(--hid-bg)
879
    }
880
 
881
    .bbar.fast .btrack {
882
      background: var(--fast-bg)
883
    }
884
 
885
    .bbar.run .btrack {
886
      background: var(--run-bg)
887
    }
888
 
889
    .bbar.slow .btrack {
890
      background: var(--slow-bg)
891
    }
892
 
893
    .bbar.other .btrack {
894
      background: var(--other-bg)
895
    }
896
 
897
    .bbar .btrack > i {
898
      display: block;
899
      height: 100%;
900
      border-radius: 8px;
901
      transition: width .3s ease;
902
      position: relative
903
    }
904
 
905
    .bbar.hid .btrack > i {
906
      background: linear-gradient(90deg, #9775fa, var(--hid))
907
    }
908
 
909
    .bbar.fast .btrack > i {
910
      background: linear-gradient(90deg, var(--brand2), var(--fast))
911
    }
912
 
913
    .bbar.run .btrack > i {
914
      background: linear-gradient(90deg, #20c997, var(--run))
915
    }
916
 
917
    .bbar.slow .btrack > i {
918
      background: linear-gradient(90deg, #ffa94d, var(--slow))
919
    }
920
 
921
    .bbar.other .btrack > i {
922
      background: linear-gradient(90deg, #adb5bd, var(--other))
923
    }
924
 
925
    .bbar .btgt {
926
      font-size: 12px;
927
      color: var(--muted);
928
      font-variant-numeric: tabular-nums;
929
      font-weight: 600;
930
      text-align: right
931
    }
932
 
933
    .bbar .btgt b {
934
      color: var(--ink);
935
      font-weight: 700
936
    }
937
 
938
    .bbar .bpct {
939
      font-size: 13px;
940
      font-weight: 750;
941
      text-align: right;
942
      font-variant-numeric: tabular-nums
943
    }
944
 
945
    .bbar .bpct.good {
946
      color: var(--good)
947
    }
948
 
949
    .bbar .bpct.warn {
950
      color: var(--warn)
951
    }
952
 
953
    .bbar .bpct.bad {
954
      color: var(--bad)
955
    }
956
 
957
    .bbar .bpct.na {
958
      color: var(--soft);
959
      font-weight: 600
960
    }
961
 
962
    /* brand mix bars in drill-down */
963
    .brands {
964
      display: flex;
965
      flex-direction: column;
966
      gap: 8px;
967
      margin-bottom: 20px
968
    }
969
 
970
    .brand-card {
971
      background: #fff;
972
      border: 1px solid var(--line);
973
      border-radius: 11px;
974
      overflow: hidden;
975
      transition: box-shadow .12s
976
    }
977
 
978
    .brand-card:hover {
979
      box-shadow: 0 3px 10px rgba(20, 30, 60, .06)
980
    }
981
 
982
    .brand-card.open {
983
      box-shadow: 0 4px 14px rgba(59, 91, 219, .14);
984
      border-color: #c8d2f4
985
    }
986
 
987
    .brand-head {
988
      display: grid;
989
      grid-template-columns:24px 130px 1fr 100px 60px;
990
      gap: 12px;
991
      align-items: center;
992
      padding: 10px 14px;
993
      cursor: pointer;
994
      user-select: none
995
    }
996
 
997
    .brand-head .cx {
998
      color: #c5cce0;
999
      font-size: 14px;
1000
      transition: transform .18s ease, color .12s
1001
    }
1002
 
1003
    .brand-card.open .brand-head .cx {
1004
      transform: rotate(90deg);
1005
      color: var(--brand)
1006
    }
1007
 
1008
    .brand-head .bn {
1009
      font-size: 13px;
1010
      font-weight: 700;
1011
      color: var(--ink);
1012
      white-space: nowrap;
1013
      overflow: hidden;
1014
      text-overflow: ellipsis
1015
    }
1016
 
1017
    .brand-head .btrack {
1018
      height: 10px;
1019
      background: #eef1f6;
1020
      border-radius: 6px;
1021
      overflow: hidden
1022
    }
1023
 
1024
    .brand-head .btrack > i {
1025
      display: block;
1026
      height: 100%;
1027
      border-radius: 6px;
1028
      background: linear-gradient(90deg, var(--brand2), var(--brand));
1029
      transition: width .3s ease
1030
    }
1031
 
1032
    .brand-head .bval {
1033
      font-size: 13px;
1034
      font-weight: 700;
1035
      color: var(--ink);
1036
      font-variant-numeric: tabular-nums;
1037
      text-align: right
1038
    }
1039
 
1040
    .brand-head .bshare {
1041
      font-size: 12px;
1042
      font-weight: 700;
1043
      color: var(--muted);
1044
      font-variant-numeric: tabular-nums;
1045
      text-align: right
1046
    }
1047
 
1048
    .brand-card.top .brand-head .btrack > i {
1049
      background: linear-gradient(90deg, #38b2ac, var(--teal))
1050
    }
1051
 
1052
    .brand-card.top .brand-head .bshare {
1053
      color: var(--teal)
1054
    }
1055
 
1056
    .brand-body {
1057
      display: none;
1058
      padding: 6px 14px 14px;
1059
      border-top: 1px dashed var(--line);
1060
      background: #fafbfe
1061
    }
1062
 
1063
    .brand-card.open .brand-body {
1064
      display: block
1065
    }
1066
 
1067
    .brand-body .bhdr {
1068
      font-size: 10.5px;
1069
      letter-spacing: .06em;
1070
      text-transform: uppercase;
1071
      color: var(--soft);
1072
      font-weight: 700;
1073
      margin: 8px 0 8px
1074
    }
1075
 
1076
    .mbars {
1077
      display: flex;
1078
      flex-direction: column;
1079
      gap: 6px
1080
    }
1081
 
1082
    .mbar {
1083
      display: grid;
1084
      grid-template-columns:90px 1fr 100px 60px;
1085
      gap: 10px;
1086
      align-items: center;
1087
      padding: 6px 10px;
1088
      background: #fff;
1089
      border: 1px solid var(--line2);
1090
      border-radius: 8px
1091
    }
1092
 
1093
    .mbar .mname {
1094
      font-size: 11px;
1095
      font-weight: 700;
1096
      letter-spacing: .04em;
1097
      text-transform: uppercase;
1098
      display: flex;
1099
      align-items: center;
1100
      gap: 5px
1101
    }
1102
 
1103
    .mbar .mname i {
1104
      display: inline-block;
1105
      width: 7px;
1106
      height: 7px;
1107
      border-radius: 50%
1108
    }
1109
 
1110
    .mbar.hid .mname {
1111
      color: var(--hid)
1112
    }
1113
 
1114
    .mbar.hid .mname i {
1115
      background: var(--hid)
1116
    }
1117
 
1118
    .mbar.fastmoving .mname {
1119
      color: var(--fast)
1120
    }
1121
 
1122
    .mbar.fastmoving .mname i {
1123
      background: var(--fast)
1124
    }
1125
 
1126
    .mbar.running .mname {
1127
      color: var(--run)
1128
    }
1129
 
1130
    .mbar.running .mname i {
1131
      background: var(--run)
1132
    }
1133
 
1134
    .mbar.slowmoving .mname {
1135
      color: var(--slow)
1136
    }
1137
 
1138
    .mbar.slowmoving .mname i {
1139
      background: var(--slow)
1140
    }
1141
 
1142
    .mbar.other .mname {
1143
      color: var(--other)
1144
    }
1145
 
1146
    .mbar.other .mname i {
1147
      background: var(--other)
1148
    }
1149
 
1150
    .mbar .mtrack {
1151
      height: 10px;
1152
      border-radius: 5px;
1153
      overflow: hidden
1154
    }
1155
 
1156
    .mbar.hid .mtrack {
1157
      background: var(--hid-bg)
1158
    }
1159
 
1160
    .mbar.fastmoving .mtrack {
1161
      background: var(--fast-bg)
1162
    }
1163
 
1164
    .mbar.running .mtrack {
1165
      background: var(--run-bg)
1166
    }
1167
 
1168
    .mbar.slowmoving .mtrack {
1169
      background: var(--slow-bg)
1170
    }
1171
 
1172
    .mbar.other .mtrack {
1173
      background: var(--other-bg)
1174
    }
1175
 
1176
    .mbar .mtrack > i {
1177
      display: block;
1178
      height: 100%;
1179
      border-radius: 5px;
1180
      transition: width .3s ease
1181
    }
1182
 
1183
    .mbar.hid .mtrack > i {
1184
      background: linear-gradient(90deg, #9775fa, var(--hid))
1185
    }
1186
 
1187
    .mbar.fastmoving .mtrack > i {
1188
      background: linear-gradient(90deg, var(--brand2), var(--fast))
1189
    }
1190
 
1191
    .mbar.running .mtrack > i {
1192
      background: linear-gradient(90deg, #20c997, var(--run))
1193
    }
1194
 
1195
    .mbar.slowmoving .mtrack > i {
1196
      background: linear-gradient(90deg, #ffa94d, var(--slow))
1197
    }
1198
 
1199
    .mbar.other .mtrack > i {
1200
      background: linear-gradient(90deg, #adb5bd, var(--other))
1201
    }
1202
 
1203
    .mbar .mval {
1204
      font-size: 12px;
1205
      font-weight: 650;
1206
      color: var(--ink);
1207
      font-variant-numeric: tabular-nums;
1208
      text-align: right
1209
    }
1210
 
1211
    .mbar .mshare {
1212
      font-size: 12px;
1213
      font-weight: 700;
1214
      font-variant-numeric: tabular-nums;
1215
      text-align: right;
1216
      color: var(--muted)
1217
    }
1218
 
1219
    /* status list modal (achieved vs pending) */
1220
    .status-back {
1221
      position: fixed;
1222
      inset: 0;
1223
      background: rgba(15, 22, 40, .5);
1224
      backdrop-filter: blur(2px);
1225
      z-index: 1300;
1226
      display: none;
1227
      align-items: flex-start;
1228
      justify-content: center;
1229
      padding: 60px 20px;
1230
      overflow-y: auto
1231
    }
1232
 
1233
    .status-back.show {
1234
      display: flex
1235
    }
1236
 
1237
    .status-modal {
1238
      background: #fff;
1239
      border-radius: 16px;
1240
      max-width: 1000px;
1241
      width: 100%;
1242
      box-shadow: 0 20px 60px rgba(20, 30, 60, .28);
1243
      padding: 22px 24px 20px;
1244
      position: relative
1245
    }
1246
 
1247
    .status-modal .x {
1248
      position: absolute;
1249
      top: 14px;
1250
      right: 16px;
1251
      cursor: pointer;
1252
      color: var(--soft);
1253
      font-size: 24px;
1254
      line-height: 1;
1255
      border: 0;
1256
      background: none;
1257
      width: 32px;
1258
      height: 32px;
1259
      border-radius: 8px;
1260
      transition: background .1s
1261
    }
1262
 
1263
    .status-modal .x:hover {
1264
      background: #f1f3f9;
1265
      color: var(--ink)
1266
    }
1267
 
1268
    .status-modal h3 {
1269
      margin: 0 0 3px;
1270
      font-size: 19px;
1271
      font-weight: 760;
1272
      letter-spacing: -.01em;
1273
      padding-right: 40px
1274
    }
1275
 
1276
    .status-modal > .desc {
1277
      margin: 0 0 18px;
1278
      font-size: 12.5px;
1279
      color: var(--muted);
1280
      font-weight: 600
1281
    }
1282
 
1283
    .status-grid {
1284
      display: grid;
1285
      grid-template-columns:1fr 1fr;
1286
      gap: 18px
1287
    }
1288
 
1289
    .status-col {
1290
      border: 1px solid var(--line);
1291
      border-radius: 12px;
1292
      padding: 14px 16px;
1293
      background: #fbfcfe
1294
    }
1295
 
1296
    .status-col.good {
1297
      border-color: #c3e8d0;
1298
      background: #f4fbf6
1299
    }
1300
 
1301
    .status-col.bad {
1302
      border-color: #f2c4c4;
1303
      background: #fdf5f5
1304
    }
1305
 
1306
    .status-col h4 {
1307
      margin: 0 0 10px;
1308
      font-size: 13px;
1309
      font-weight: 730;
1310
      display: flex;
1311
      align-items: center;
1312
      gap: 8px;
1313
      letter-spacing: -.005em
1314
    }
1315
 
1316
    .status-col h4 .cnt {
1317
      background: #fff;
1318
      border: 1px solid var(--line);
1319
      color: var(--ink);
1320
      font-size: 11px;
1321
      font-weight: 700;
1322
      padding: 2px 9px;
1323
      border-radius: 999px
1324
    }
1325
 
1326
    .status-col.good h4 {
1327
      color: #177a3a
1328
    }
1329
 
1330
    .status-col.good h4 .cnt {
1331
      background: #e5f7ee;
1332
      border-color: #c3e8d0;
1333
      color: #177a3a
1334
    }
1335
 
1336
    .status-col.bad h4 {
1337
      color: #b8272b
1338
    }
1339
 
1340
    .status-col.bad h4 .cnt {
1341
      background: #ffeaea;
1342
      border-color: #f2c4c4;
1343
      color: #b8272b
1344
    }
1345
 
1346
    .status-list {
1347
      display: flex;
1348
      flex-direction: column;
1349
      gap: 6px;
1350
      max-height: 60vh;
1351
      overflow-y: auto;
1352
      padding-right: 4px
1353
    }
1354
 
1355
    .status-item {
1356
      display: grid;
1357
      grid-template-columns:1fr 100px 68px;
1358
      gap: 10px;
1359
      align-items: center;
1360
      padding: 9px 12px;
1361
      background: #fff;
1362
      border: 1px solid var(--line2);
1363
      border-radius: 9px;
1364
      transition: box-shadow .12s
1365
    }
1366
 
1367
    .status-item:hover {
1368
      box-shadow: 0 2px 8px rgba(20, 30, 60, .06)
1369
    }
1370
 
1371
    .status-item .rn {
1372
      font-weight: 700;
1373
      color: var(--ink);
1374
      font-size: 12.5px;
1375
      line-height: 1.3
1376
    }
1377
 
1378
    .status-item .wh {
1379
      font-size: 11px;
1380
      color: var(--muted);
1381
      font-weight: 600;
1382
      display: block;
1383
      margin-top: 2px
1384
    }
1385
 
1386
    .status-item .vv {
1387
      font-size: 12px;
1388
      color: var(--muted);
1389
      font-variant-numeric: tabular-nums;
1390
      font-weight: 600;
1391
      text-align: right;
1392
      white-space: nowrap
1393
    }
1394
 
1395
    .status-item .vv b {
1396
      color: var(--ink);
1397
      font-weight: 700
1398
    }
1399
 
1400
    .status-item .pp {
1401
      font-size: 12px;
1402
      font-weight: 700;
1403
      font-variant-numeric: tabular-nums;
1404
      text-align: right
1405
    }
1406
 
1407
    .status-item .pp.good {
1408
      color: var(--good)
1409
    }
1410
 
1411
    .status-item .pp.warn {
1412
      color: var(--warn)
1413
    }
1414
 
1415
    .status-item .pp.bad {
1416
      color: var(--bad)
1417
    }
1418
 
1419
    .status-empty {
1420
      padding: 24px;
1421
      text-align: center;
1422
      color: var(--soft);
1423
      font-size: 12.5px
1424
    }
1425
 
1426
    @media (max-width: 960px) {
1427
      .kpis {
1428
        grid-template-columns:repeat(2, 1fr)
1429
      }
1430
 
1431
      .brand h1 {
1432
        font-size: 22px
1433
      }
1434
 
1435
      .status-grid {
1436
        grid-template-columns:1fr
1437
      }
1438
    }
1439
  </style>
1440
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css"/>
1441
</head>
1442
<body>
1443
<div class="wrap">
1444
  <header class="top">
1445
    <div class="brand">
1446
      <div class="crumb"><a href="/indent/today_po_rbm">&larr; Today PO / RBM</a> &middot; Sales &middot; Indent</div>
1447
      <h1>RBM DRR Dashboard <span class="live"><i></i>Live</span></h1>
1448
    </div>
1449
    <div class="hd-right">
1450
      <input class="ctl" id="rdrrPeriod" type="text" placeholder="Select date range"/>
1451
      <button class="btn-r" id="rdrrApply">Apply</button>
1452
    </div>
1453
  </header>
1454
 
1455
  <div class="kpis">
1456
    <div class="kpi a">
1457
      <div class="top">
1458
        <div class="ic">
1459
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"
1460
               stroke-linejoin="round">
1461
            <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
1462
          </svg>
1463
        </div>
1464
        <div class="label">Total Achieved</div>
1465
      </div>
1466
      <div class="val" id="rdrrKpiAch">&mdash;</div>
1467
      <div class="foot" id="rdrrKpiAchSub">Actual billed across all RBMs</div>
1468
    </div>
1469
    <div class="kpi b">
1470
      <div class="top">
1471
        <div class="ic">
1472
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"
1473
               stroke-linejoin="round">
1474
            <circle cx="12" cy="12" r="9"/>
1475
            <circle cx="12" cy="12" r="5"/>
1476
            <circle cx="12" cy="12" r="1.5" fill="currentColor"/>
1477
          </svg>
1478
        </div>
1479
        <div class="label">Total Target</div>
1480
      </div>
1481
      <div class="val" id="rdrrKpiTgt">&mdash;</div>
1482
      <div class="foot" id="rdrrKpiTgtSub">Sum of daily target snapshots</div>
1483
    </div>
1484
    <div class="kpi c">
1485
      <div class="top">
1486
        <div class="ic">
1487
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"
1488
               stroke-linejoin="round">
1489
            <path d="M3 17l6-6 4 4 8-8"/>
1490
            <path d="M17 7h4v4"/>
1491
          </svg>
1492
        </div>
1493
        <div class="label">Achievement %</div>
1494
      </div>
1495
      <div class="val" id="rdrrKpiPct">&mdash;</div>
1496
      <div class="foot" id="rdrrKpiPctSub">Total ACH / Total TGT</div>
1497
    </div>
1498
    <div class="kpi d clickable" id="rdrrKpiTopCard" title="Click to see achieved vs pending RBMs">
1499
      <div class="top">
1500
        <div class="ic">
1501
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"
1502
               stroke-linejoin="round">
1503
            <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"/>
1504
          </svg>
1505
        </div>
1506
        <div class="label">RBMs ≥ 100% Target</div>
1507
      </div>
1508
      <div class="val" id="rdrrKpiTop">&mdash;</div>
1509
      <div class="foot" id="rdrrKpiTopSub">Rows meeting or beating target</div>
1510
      <span class="hint">Click for list &rsaquo;</span>
1511
    </div>
1512
  </div>
1513
 
1514
  <div class="sectionlabel">RBM &times; Warehouse &times; Movement Bucket</div>
1515
  <div class="legend">
1516
    <span class="lg hid"><i></i><span class="lgt">HID</span></span>
1517
    <span class="lg fast"><i></i><span class="lgt">FAST</span></span>
1518
    <span class="lg run"><i></i><span class="lgt">RUN</span></span>
1519
    <span class="lg slow"><i></i><span class="lgt">SLOW</span></span>
1520
    <span class="lg other"><i></i><span class="lgt">OTHER</span></span>
1521
  </div>
1522
  <div class="card">
1523
    <div class="card-hd">
1524
      <div>
1525
        <h2>Achievement vs Target</h2>
1526
        <p class="desc">Click any row for a per-bucket breakdown and the catalogs sold.</p>
1527
      </div>
1528
      <span class="badge-period" id="rdrrPeriodLabel">&mdash;</span>
1529
    </div>
1530
    <div id="rdrrTableWrap">
1531
      <div class="loading">
1532
        <div class="spinner"></div>
1533
        Loading&hellip;
1534
      </div>
1535
    </div>
1536
    <div class="foot-note">
1537
      <b>TGT</b> is summed from persisted per-day snapshots (<code>inventory.rbm_targets</code>) — days with no
1538
      scheduler-written snapshot contribute 0.
1539
      <b>ACH</b> is computed from source orders (<code>transaction.order</code>) for the range.
1540
    </div>
1541
  </div>
1542
</div>
1543
 
1544
<!-- achieved-vs-pending modal (opens from the RBMs ≥ 100% Target KPI card) -->
1545
<div class="status-back" id="rdrrStatusBack">
1546
  <div class="status-modal">
1547
    <button class="x" id="rdrrStatusClose" aria-label="Close">&times;</button>
1548
    <h3>Target Status by RBM</h3>
1549
    <p class="desc" id="rdrrStatusDesc">&mdash;</p>
1550
    <div class="status-grid">
1551
      <div class="status-col good">
1552
        <h4>✅ Achieved <span class="cnt" id="rdrrStatusAchCnt">0</span></h4>
1553
        <div class="status-list" id="rdrrStatusAchList"></div>
1554
      </div>
1555
      <div class="status-col bad">
1556
        <h4>⚠ Pending <span class="cnt" id="rdrrStatusPndCnt">0</span></h4>
1557
        <div class="status-list" id="rdrrStatusPndList"></div>
1558
      </div>
1559
    </div>
1560
  </div>
1561
</div>
1562
 
1563
<!-- drill-down panel -->
1564
<div class="rdrr-back" id="rdrrBack">
1565
  <div class="rdrr-panel">
1566
    <button class="x" id="rdrrClose" aria-label="Close">&times;</button>
1567
    <h3 id="rdrrDdTitle">&mdash;</h3>
1568
    <p class="desc" id="rdrrDdDesc">&mdash;</p>
1569
 
1570
    <div class="rdrr-total">
1571
      <div>
1572
        <div class="k">Achieved</div>
1573
        <div class="v" id="rdrrDdAch">&mdash;</div>
1574
      </div>
1575
      <div>
1576
        <div class="k">Target</div>
1577
        <div class="v" id="rdrrDdTgt">&mdash;</div>
1578
      </div>
1579
      <div>
1580
        <div class="k">Achievement</div>
1581
        <div class="v" id="rdrrDdPct">&mdash;</div>
1582
      </div>
1583
    </div>
1584
 
1585
    <div class="sectionlabel" style="margin-top:2px">Movement Split — Achievement vs Target</div>
1586
    <div class="bbars" id="rdrrDdBars"></div>
1587
 
1588
    <div class="sectionlabel">Brand Mix — Share of Achievement <span
1589
            style="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(click a brand for movement split)</span>
1590
    </div>
1591
    <div id="rdrrDdBrands">
1592
      <div class="loading">
1593
        <div class="spinner"></div>
1594
        Loading brand mix&hellip;
1595
      </div>
1596
    </div>
1597
  </div>
1598
</div>
1599
 
1600
<script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
1601
<script src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
1602
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
1603
<script>
1604
  (function () {
1605
    var DEFAULT_START = "$defaultStartDate";
1606
    var DEFAULT_END = "$defaultEndDate";
1607
 
1608
    // Prefer query-string range if the caller provided one (e.g. from today_po_rbm's date picker).
1609
    var url = new URL(window.location.href);
1610
    var qsStart = url.searchParams.get("startDate");
1611
    var qsEnd = url.searchParams.get("endDate");
1612
    var startDate = qsStart || DEFAULT_START;
1613
    var endDate = qsEnd || DEFAULT_END;
1614
 
1615
    function fmtDate(s) {
1616
      return moment(s).format("DD MMM");
1617
    }
1618
 
1619
    function fmtLakh(v) {
1620
      v = Number(v) || 0;
1621
      return (v / 100000).toFixed(1) + "L";
1622
    }
1623
 
1624
    function pctClass(p) {
1625
      if (p >= 95) return "good";
1626
      if (p >= 41) return "warn";
1627
      return "bad";
1628
    }
1629
 
1630
    function pctLabel(ach, tgt) {
1631
      if (!tgt || tgt <= 0) return {text: "-", cls: "na", num: null};
1632
      var p = (ach / tgt) * 100;
1633
      return {text: p.toFixed(1) + "%", cls: pctClass(p), num: p};
1634
    }
1635
 
1636
    function pctBarWidth(ach, tgt) {
1637
      if (!tgt || tgt <= 0) return 0;
1638
      return Math.min(100, (ach / tgt) * 100);
1639
    }
1640
 
1641
    // Initialise the range picker.
1642
    $("#rdrrPeriod").daterangepicker({
1643
      startDate: moment(startDate),
1644
      endDate: moment(endDate),
1645
      locale: {format: "YYYY-MM-DD", cancelLabel: "Clear"}
1646
    });
1647
    $("#rdrrPeriod").on("apply.daterangepicker", function (ev, picker) {
1648
      startDate = picker.startDate.format("YYYY-MM-DD");
1649
      endDate = picker.endDate.format("YYYY-MM-DD");
1650
    });
1651
 
1652
    // Defensive: read the picker's current state at click time in case
1653
    // the apply.daterangepicker event was swallowed for any reason.
1654
    $("#rdrrApply").on("click", function () {
1655
      var p = $("#rdrrPeriod").data("daterangepicker");
1656
      if (p) {
1657
        startDate = p.startDate.format("YYYY-MM-DD");
1658
        endDate = p.endDate.format("YYYY-MM-DD");
1659
      }
1660
      load();
1661
    });
1662
 
1663
    function load() {
1664
      $("#rdrrTableWrap").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
1665
      $("#rdrrPeriodLabel").text(fmtDate(startDate) + " – " + fmtDate(endDate));
1666
      fetch("/rbm-drr/data?startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"})
1667
              .then(function (r) {
1668
                return r.json().then(function (b) {
1669
                  return {ok: r.ok, body: b};
1670
                });
1671
              })
1672
              .then(function (res) {
1673
                if (!res.ok) {
1674
                  $("#rdrrTableWrap").html('<div class="empty">' + escapeHtml(res.body && res.body.error ? res.body.error : "Failed to load.") + '</div>');
1675
                  return;
1676
                }
1677
                render(res.body);
1678
              })
1679
              .catch(function (e) {
1680
                $("#rdrrTableWrap").html('<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>');
1681
              });
1682
    }
1683
 
1684
    // Cached latest payload so the "Target Status by RBM" modal can render
1685
    // without a re-fetch when the KPI card is clicked.
1686
    var LAST_PAYLOAD = null;
1687
 
1688
    function render(data) {
1689
      LAST_PAYLOAD = data;
1690
      var rows = data.rows || [];
1691
      var t = data.totals || {};
1692
 
1693
      $("#rdrrKpiAch").html('&#8377;&nbsp;' + fmtLakh(t.totalAch));
1694
      $("#rdrrKpiTgt").html('&#8377;&nbsp;' + fmtLakh(t.totalTgt));
1695
      var pk = pctLabel(t.totalAch, t.totalTgt);
1696
      $("#rdrrKpiPct").html(pk.cls === "na" ? '<span class="u">n/a</span>' :
1697
              '<span class="pct ' + pk.cls + '">' + pk.text + '</span>');
1698
      $("#rdrrKpiTop").html((t.rbmsAtOrAbove100 || 0) + ' <span class="u">/ ' + (t.rbmRowCount || 0) + '</span>');
1699
 
1700
      if (rows.length === 0) {
1701
        $("#rdrrTableWrap").html('<div class="empty"><div class="ei">📭</div>No RBM rows in this range.</div>');
1702
        return;
1703
      }
1704
 
1705
      var buckets = [
1706
        {key: "hid", label: "HID"},
1707
        {key: "fast", label: "FAST"},
1708
        {key: "run", label: "RUN"},
1709
        {key: "slow", label: "SLOW"},
1710
        {key: "other", label: "OTHER"}
1711
      ];
1712
 
1713
      var html = ['<table id="rdrrGrid"><thead><tr>',
1714
        '<th>RBM &middot; Warehouse</th>',
1715
        '<th class="num g-total">Total<br><small style="color:var(--soft);font-weight:600">ACH / TGT</small></th>',
1716
        '<th class="num g-total">%</th>'];
1717
      buckets.forEach(function (b) {
1718
        html.push('<th class="num g-' + b.key + '">' + b.label + '<br><small style="color:var(--soft);font-weight:600">ACH / TGT</small></th>');
1719
        html.push('<th class="num g-' + b.key + '">%</th>');
1720
      });
1721
      html.push('<th></th></tr></thead><tbody>');
1722
 
1723
      rows.forEach(function (r) {
1724
        var tr = ['<tr class="clickable" data-authid="' + r.authId + '" data-warehouseid="' + r.warehouseId + '" data-rbmname="' + escapeHtml(r.rbmName) + '" data-warehousename="' + escapeHtml(r.warehouseName) + '">'];
1725
        tr.push('<td><div class="rbm-cell"><span class="rn">' + escapeHtml(r.rbmName) + '</span><span class="wh">' + escapeHtml(r.warehouseName) + '</span></div></td>');
1726
        tr.push(cellPair(r.totalAch, r.totalTgt));
1727
        buckets.forEach(function (b) {
1728
          tr.push(cellPair(r[b.key + "Ach"], r[b.key + "Tgt"]));
1729
        });
1730
        tr.push('<td class="chev">&rsaquo;</td>');
1731
        tr.push('</tr>');
1732
        html.push(tr.join(""));
1733
      });
1734
 
1735
      // totals row
1736
      var totTr = ['<tr class="totalrow"><td><strong>Total</strong></td>'];
1737
      totTr.push(cellPair(t.totalAch, t.totalTgt, true));
1738
      buckets.forEach(function (b) {
1739
        totTr.push(cellPair(t[b.key + "Ach"], t[b.key + "Tgt"], true));
1740
      });
1741
      totTr.push('<td></td></tr>');
1742
      html.push(totTr.join(""));
1743
 
1744
      html.push('</tbody></table>');
1745
      $("#rdrrTableWrap").html(html.join(""));
1746
 
1747
      // Cache the parsed row on each <tr> so drill-down can render bars immediately.
1748
      rows.forEach(function (r) {
1749
        $("#rdrrGrid tbody tr").filter(function () {
1750
          return +this.dataset.authid === r.authId && +this.dataset.warehouseid === r.warehouseId;
1751
        }).each(function () {
1752
          $(this).data("cached", r);
1753
        });
1754
      });
1755
 
1756
      $("#rdrrGrid tbody tr.clickable").on("click", function () {
1757
        var d = this.dataset;
1758
        var cached = $(this).data("cached");
1759
        openDrilldown(+d.authid, +d.warehouseid, d.rbmname, d.warehousename, cached);
1760
      });
1761
    }
1762
 
1763
    function cellPair(ach, tgt, isTotal) {
1764
      var p = pctLabel(ach, tgt);
1765
      var vhtml = '<td class="num"><div class="cell-val">' + fmtLakh(ach) + ' <small>/ ' + fmtLakh(tgt) + '</small></div></td>';
1766
      var barCls = p.cls === "na" ? "" : p.cls;
1767
      var barW = pctBarWidth(ach, tgt);
1768
      var pctHtml = '<td class="num"><span class="pct ' + p.cls + '">' + p.text + '</span>' +
1769
              (barCls ? '<div class="bar"><i class="' + barCls + '" style="width:' + barW.toFixed(1) + '%"></i></div>' : '') +
1770
              '</td>';
1771
      return vhtml + pctHtml;
1772
    }
1773
 
1774
    // --------------------------- drill-down ---------------------------
1775
 
1776
    function openDrilldown(authId, warehouseId, rbmName, warehouseName, cachedRow) {
1777
      var back = $("#rdrrBack");
1778
      $("#rdrrDdTitle").text(rbmName + " — " + warehouseName);
1779
      $("#rdrrDdDesc").text(fmtDate(startDate) + " – " + fmtDate(endDate));
1780
      $("#rdrrDdBars").empty();
1781
      back.addClass("show");
1782
 
1783
      if (cachedRow) {
1784
        renderBars(cachedRow);
1785
      } else {
1786
        $("#rdrrDdAch").text("—");
1787
        $("#rdrrDdTgt").text("—");
1788
        $("#rdrrDdPct").text("—");
1789
      }
1790
      $("#rdrrDdBrands").html('<div class="loading"><div class="spinner"></div>Loading brand mix&hellip;</div>');
1791
 
1792
      fetch("/rbm-drr/rbm-detail?authId=" + authId + "&warehouseId=" + warehouseId +
1793
              "&startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"})
1794
              .then(function (r) {
1795
                return r.json();
1796
              })
1797
              .then(function (data) {
1798
                renderBrands(data);
1799
              })
1800
              .catch(function (e) {
1801
                $("#rdrrDdBrands").html('<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>');
1802
              });
1803
    }
1804
 
1805
    function renderBrands(data) {
1806
      var brands = (data && data.brands) || [];
1807
      if (brands.length === 0) {
1808
        $("#rdrrDdBrands").html('<div class="empty"><div class="ei">🏷️</div>No brand sales in this range.</div>');
1809
        return;
1810
      }
1811
      var maxShare = brands[0].sharePct || 0.001;
1812
      var html = '<div class="brands">';
1813
      brands.forEach(function (b, idx) {
1814
        var barW = maxShare > 0 ? Math.min(100, (b.sharePct / maxShare) * 100) : 0;
1815
        var topCls = idx === 0 ? ' top' : '';
1816
        html += '<div class="brand-card' + topCls + '" data-idx="' + idx + '">' +
1817
                '<div class="brand-head">' +
1818
                '<div class="cx">&rsaquo;</div>' +
1819
                '<div class="bn" title="' + escapeHtml(b.brand) + '">' + escapeHtml(b.brand) + '</div>' +
1820
                '<div class="btrack"><i style="width:' + barW.toFixed(1) + '%"></i></div>' +
1821
                '<div class="bval">' + fmtLakh(b.valueSold) + '</div>' +
1822
                '<div class="bshare">' + (b.sharePct || 0).toFixed(1) + '%</div>' +
1823
                '</div>' +
1824
                '<div class="brand-body">' +
1825
                '<div class="bhdr">Movement Split within ' + escapeHtml(b.brand) + '</div>' +
1826
                renderBrandBuckets(b.buckets || []) +
1827
                '</div>' +
1828
                '</div>';
1829
      });
1830
      html += '</div>';
1831
      $("#rdrrDdBrands").html(html);
1832
 
1833
      $("#rdrrDdBrands .brand-head").on("click", function () {
1834
        $(this).closest(".brand-card").toggleClass("open");
1835
      });
1836
    }
1837
 
1838
    function renderBrandBuckets(buckets) {
1839
      if (!buckets || buckets.length === 0) {
1840
        return '<div style="color:var(--soft);font-size:12px;padding:8px 4px">No movement split available.</div>';
1841
      }
1842
      var labels = {HID: "HID", FASTMOVING: "FAST", RUNNING: "RUN", SLOWMOVING: "SLOW", OTHER: "OTHER"};
1843
      var html = '<div class="mbars">';
1844
      buckets.forEach(function (bk) {
1845
        var cls = (bk.status || "OTHER").toLowerCase();
1846
        var label = labels[bk.status] || bk.status || "OTHER";
1847
        var sp = bk.sharePctOfBrand || 0;
1848
        var barW = Math.min(100, sp);
1849
        html += '<div class="mbar ' + cls + '">' +
1850
                '<div class="mname"><i></i>' + label + '</div>' +
1851
                '<div class="mtrack"><i style="width:' + barW.toFixed(1) + '%"></i></div>' +
1852
                '<div class="mval">' + fmtLakh(bk.valueSold) + '</div>' +
1853
                '<div class="mshare">' + sp.toFixed(1) + '%</div>' +
1854
                '</div>';
1855
      });
1856
      html += '</div>';
1857
      return html;
1858
    }
1859
 
1860
    function renderBars(row) {
1861
      var totalAch = row.totalAch, totalTgt = row.totalTgt;
1862
      $("#rdrrDdAch").html('&#8377;&nbsp;' + fmtLakh(totalAch));
1863
      $("#rdrrDdTgt").html('&#8377;&nbsp;' + fmtLakh(totalTgt));
1864
      var pt = pctLabel(totalAch, totalTgt);
1865
      $("#rdrrDdPct").html(pt.cls === "na" ? '<span style="color:var(--soft);font-size:14px">n/a</span>' :
1866
              '<span class="pct ' + pt.cls + '">' + pt.text + '</span>');
1867
 
1868
      var buckets = [
1869
        {key: "hid", label: "HID"},
1870
        {key: "fast", label: "FAST"},
1871
        {key: "run", label: "RUN"},
1872
        {key: "slow", label: "SLOW"},
1873
        {key: "other", label: "OTHER"}
1874
      ];
1875
      var html = "";
1876
      buckets.forEach(function (b) {
1877
        var ach = row[b.key + "Ach"], tgt = row[b.key + "Tgt"];
1878
        var p = pctLabel(ach, tgt);
1879
        var w = pctBarWidth(ach, tgt);
1880
        html += '<div class="bbar ' + b.key + '">' +
1881
                '<div class="bname"><i></i>' + b.label + '</div>' +
1882
                '<div class="btrack"><i style="width:' + w.toFixed(1) + '%"></i></div>' +
1883
                '<div class="btgt"><b>' + fmtLakh(ach) + '</b> / ' + fmtLakh(tgt) + '</div>' +
1884
                '<div class="bpct ' + p.cls + '">' + p.text + '</div>' +
1885
                '</div>';
1886
      });
1887
      $("#rdrrDdBars").html(html);
1888
    }
1889
 
1890
    function escapeHtml(s) {
1891
      if (s == null) return "";
1892
      return String(s).replace(/[&<>"']/g, function (c) {
1893
        return {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}[c];
1894
      });
1895
    }
1896
 
1897
    $("#rdrrClose").on("click", function () {
1898
      $("#rdrrBack").removeClass("show");
1899
    });
1900
    $("#rdrrBack").on("click", function (e) {
1901
      if (e.target === this) $(this).removeClass("show");
1902
    });
1903
 
1904
    // Target-status modal (achieved vs pending) — opens from the KPI card.
1905
    $("#rdrrKpiTopCard").on("click", function () {
1906
      openStatusModal();
1907
    });
1908
    $("#rdrrStatusClose").on("click", function () {
1909
      $("#rdrrStatusBack").removeClass("show");
1910
    });
1911
    $("#rdrrStatusBack").on("click", function (e) {
1912
      if (e.target === this) $(this).removeClass("show");
1913
    });
1914
 
1915
    function openStatusModal() {
1916
      if (!LAST_PAYLOAD) return;
1917
      var rows = (LAST_PAYLOAD.rows || []).slice();
1918
      var achieved = [], pending = [];
1919
      rows.forEach(function (r) {
1920
        if (!r.totalTgt || r.totalTgt <= 0) return;
1921
        var pct = (r.totalAch / r.totalTgt) * 100;
1922
        var enriched = {row: r, pct: pct};
1923
        if (r.totalAch >= r.totalTgt) achieved.push(enriched);
1924
        else pending.push(enriched);
1925
      });
1926
      achieved.sort(function (a, b) {
1927
        return b.pct - a.pct;
1928
      });
1929
      pending.sort(function (a, b) {
1930
        return a.pct - b.pct;
1931
      });
1932
 
1933
      $("#rdrrStatusDesc").text(fmtDate(startDate) + " – " + fmtDate(endDate));
1934
      $("#rdrrStatusAchCnt").text(achieved.length);
1935
      $("#rdrrStatusPndCnt").text(pending.length);
1936
      $("#rdrrStatusAchList").html(renderStatusList(achieved, "achieved"));
1937
      $("#rdrrStatusPndList").html(renderStatusList(pending, "pending"));
1938
      $("#rdrrStatusBack").addClass("show");
1939
 
1940
      // Bind click on each item to open the same drill-down slide-in.
1941
      $("#rdrrStatusBack .status-item").on("click", function () {
1942
        var d = this.dataset;
1943
        var cachedRow = null;
1944
        (LAST_PAYLOAD.rows || []).some(function (r) {
1945
          if (r.authId == d.authid && r.warehouseId == d.warehouseid) {
1946
            cachedRow = r;
1947
            return true;
1948
          }
1949
          return false;
1950
        });
1951
        $("#rdrrStatusBack").removeClass("show");
1952
        openDrilldown(+d.authid, +d.warehouseid, d.rbmname, d.warehousename, cachedRow);
1953
      });
1954
    }
1955
 
1956
    function renderStatusList(items, kind) {
1957
      if (items.length === 0) {
1958
        return '<div class="status-empty">' +
1959
                (kind === "achieved" ? "No RBM has met target in this range." : "All RBMs met target — nice!") +
1960
                '</div>';
1961
      }
1962
      var html = "";
1963
      items.forEach(function (it) {
1964
        var r = it.row;
1965
        var p = pctClass(it.pct);
1966
        html += '<div class="status-item" title="Click to view drill-down"' +
1967
                ' data-authid="' + r.authId + '" data-warehouseid="' + r.warehouseId + '"' +
1968
                ' data-rbmname="' + escapeHtml(r.rbmName) + '" data-warehousename="' + escapeHtml(r.warehouseName) + '"' +
1969
                ' style="cursor:pointer">' +
1970
                '<div><span class="rn">' + escapeHtml(r.rbmName) + '</span>' +
1971
                '<span class="wh">' + escapeHtml(r.warehouseName) + '</span></div>' +
1972
                '<div class="vv"><b>' + fmtLakh(r.totalAch) + '</b> / ' + fmtLakh(r.totalTgt) + '</div>' +
1973
                '<div class="pp ' + p + '">' + it.pct.toFixed(1) + '%</div>' +
1974
                '</div>';
1975
      });
1976
      return html;
1977
    }
1978
 
1979
    // initial load
1980
    load();
1981
  })();
1982
</script>
1983
</body>
1984
</html>