Subversion Repositories SmartDukaan

Rev

Rev 37469 | Rev 37497 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37469 Rev 37487
Line 957... Line 957...
957
    .bbar .bpct.na {
957
    .bbar .bpct.na {
958
      color: var(--soft);
958
      color: var(--soft);
959
      font-weight: 600
959
      font-weight: 600
960
    }
960
    }
961
 
961
 
-
 
962
    /* warehouse flow (buying vs selling) in drill-down */
-
 
963
    .flow-tbl {
-
 
964
      width: 100%;
-
 
965
      border-collapse: separate;
-
 
966
      border-spacing: 0;
-
 
967
      font-size: 12.5px;
-
 
968
      margin-bottom: 20px;
-
 
969
      background: #fff;
-
 
970
      border: 1px solid var(--line);
-
 
971
      border-radius: 11px;
-
 
972
      overflow: hidden
-
 
973
    }
-
 
974
 
-
 
975
    .flow-tbl th, .flow-tbl td {
-
 
976
      padding: 10px 12px;
-
 
977
      border-bottom: 1px solid var(--line2);
-
 
978
      text-align: right;
-
 
979
      font-variant-numeric: tabular-nums
-
 
980
    }
-
 
981
 
-
 
982
    .flow-tbl th {
-
 
983
      font-size: 10.5px;
-
 
984
      text-transform: uppercase;
-
 
985
      letter-spacing: .05em;
-
 
986
      color: var(--soft);
-
 
987
      font-weight: 700;
-
 
988
      background: #fafbfe;
-
 
989
      text-align: left;
-
 
990
      padding-left: 14px
-
 
991
    }
-
 
992
 
-
 
993
    .flow-tbl th.num, .flow-tbl td.num {
-
 
994
      text-align: right
-
 
995
    }
-
 
996
 
-
 
997
    .flow-tbl thead th:first-child {
-
 
998
      text-align: left
-
 
999
    }
-
 
1000
 
-
 
1001
    .flow-tbl tbody tr td:first-child {
-
 
1002
      text-align: left;
-
 
1003
      padding-left: 14px;
-
 
1004
      font-weight: 700;
-
 
1005
      letter-spacing: .03em;
-
 
1006
      text-transform: uppercase;
-
 
1007
      font-size: 11px
-
 
1008
    }
-
 
1009
 
-
 
1010
    .flow-tbl tbody tr td:first-child i {
-
 
1011
      display: inline-block;
-
 
1012
      width: 7px;
-
 
1013
      height: 7px;
-
 
1014
      border-radius: 50%;
-
 
1015
      margin-right: 6px;
-
 
1016
      vertical-align: middle
-
 
1017
    }
-
 
1018
 
-
 
1019
    .flow-tbl tbody tr:last-child td {
-
 
1020
      border-bottom: 0
-
 
1021
    }
-
 
1022
 
-
 
1023
    .flow-tbl tbody tr.hid td:first-child {
-
 
1024
      color: var(--hid)
-
 
1025
    }
-
 
1026
 
-
 
1027
    .flow-tbl tbody tr.hid td:first-child i {
-
 
1028
      background: var(--hid)
-
 
1029
    }
-
 
1030
 
-
 
1031
    .flow-tbl tbody tr.fastmoving td:first-child {
-
 
1032
      color: var(--fast)
-
 
1033
    }
-
 
1034
 
-
 
1035
    .flow-tbl tbody tr.fastmoving td:first-child i {
-
 
1036
      background: var(--fast)
-
 
1037
    }
-
 
1038
 
-
 
1039
    .flow-tbl tbody tr.running td:first-child {
-
 
1040
      color: var(--run)
-
 
1041
    }
-
 
1042
 
-
 
1043
    .flow-tbl tbody tr.running td:first-child i {
-
 
1044
      background: var(--run)
-
 
1045
    }
-
 
1046
 
-
 
1047
    .flow-tbl tbody tr.slowmoving td:first-child {
-
 
1048
      color: var(--slow)
-
 
1049
    }
-
 
1050
 
-
 
1051
    .flow-tbl tbody tr.slowmoving td:first-child i {
-
 
1052
      background: var(--slow)
-
 
1053
    }
-
 
1054
 
-
 
1055
    .flow-tbl tbody tr.other td:first-child {
-
 
1056
      color: var(--other)
-
 
1057
    }
-
 
1058
 
-
 
1059
    .flow-tbl tbody tr.other td:first-child i {
-
 
1060
      background: var(--other)
-
 
1061
    }
-
 
1062
 
-
 
1063
    .flow-tbl .cov-cell {
-
 
1064
      display: inline-flex;
-
 
1065
      align-items: center;
-
 
1066
      gap: 6px;
-
 
1067
      justify-content: flex-end
-
 
1068
    }
-
 
1069
 
-
 
1070
    .flow-tbl .cov-cell .cov-bar {
-
 
1071
      width: 60px;
-
 
1072
      height: 6px;
-
 
1073
      border-radius: 3px;
-
 
1074
      background: #eef1f6;
-
 
1075
      overflow: hidden;
-
 
1076
      flex: none
-
 
1077
    }
-
 
1078
 
-
 
1079
    .flow-tbl .cov-cell .cov-bar > i {
-
 
1080
      display: block;
-
 
1081
      height: 100%;
-
 
1082
      border-radius: 3px
-
 
1083
    }
-
 
1084
 
-
 
1085
    .flow-tbl .cov-cell.good .cov-bar > i {
-
 
1086
      background: var(--good)
-
 
1087
    }
-
 
1088
 
-
 
1089
    .flow-tbl .cov-cell.warn .cov-bar > i {
-
 
1090
      background: var(--warn)
-
 
1091
    }
-
 
1092
 
-
 
1093
    .flow-tbl .cov-cell.bad .cov-bar > i {
-
 
1094
      background: var(--bad)
-
 
1095
    }
-
 
1096
 
-
 
1097
    .flow-tbl .cov-cell.good .cov-pct {
-
 
1098
      color: var(--good);
-
 
1099
      font-weight: 700
-
 
1100
    }
-
 
1101
 
-
 
1102
    .flow-tbl .cov-cell.warn .cov-pct {
-
 
1103
      color: var(--warn);
-
 
1104
      font-weight: 700
-
 
1105
    }
-
 
1106
 
-
 
1107
    .flow-tbl .cov-cell.bad .cov-pct {
-
 
1108
      color: var(--bad);
-
 
1109
      font-weight: 700
-
 
1110
    }
-
 
1111
 
-
 
1112
    .flow-tbl .cov-cell.na {
-
 
1113
      color: var(--soft);
-
 
1114
      font-weight: 500
-
 
1115
    }
-
 
1116
 
-
 
1117
    .flow-tbl tr.tot td {
-
 
1118
      font-weight: 770;
-
 
1119
      background: #fafbfe;
-
 
1120
      color: var(--ink);
-
 
1121
      border-top: 2px solid var(--line)
-
 
1122
    }
-
 
1123
 
962
    /* brand mix bars in drill-down */
1124
    /* brand mix bars in drill-down */
963
    .brands {
1125
    .brands {
964
      display: flex;
1126
      display: flex;
965
      flex-direction: column;
1127
      flex-direction: column;
966
      gap: 8px;
1128
      gap: 8px;
Line 1906... Line 2068...
1906
    <div class="brand">
2068
    <div class="brand">
1907
      <div class="crumb"><a href="/indent/today_po_rbm">&larr; Today PO / RBM</a> &middot; Sales &middot; Indent</div>
2069
      <div class="crumb"><a href="/indent/today_po_rbm">&larr; Today PO / RBM</a> &middot; Sales &middot; Indent</div>
1908
      <h1>RBM DRR Dashboard <span class="live"><i></i>Live</span></h1>
2070
      <h1>RBM DRR Dashboard <span class="live"><i></i>Live</span></h1>
1909
    </div>
2071
    </div>
1910
    <div class="hd-right">
2072
    <div class="hd-right">
-
 
2073
      <select id="rdrrRbmFilter" class="ctl" style="min-width:220px;height:38px;padding:8px 12px">
-
 
2074
        <option value="">All RBMs</option>
-
 
2075
      </select>
1911
      <input class="ctl" id="rdrrPeriod" type="text" placeholder="Select date range"/>
2076
      <input class="ctl" id="rdrrPeriod" type="text" placeholder="Select date range"/>
1912
      <button class="btn-r" id="rdrrApply">Apply</button>
2077
      <button class="btn-r" id="rdrrApply">Apply</button>
1913
    </div>
2078
    </div>
1914
  </header>
2079
  </header>
1915
 
2080
 
Line 2160... Line 2325...
2160
    </div>
2325
    </div>
2161
 
2326
 
2162
    <div class="sectionlabel" style="margin-top:2px">Movement Split — Achievement vs Target</div>
2327
    <div class="sectionlabel" style="margin-top:2px">Movement Split — Achievement vs Target</div>
2163
    <div class="bbars" id="rdrrDdBars"></div>
2328
    <div class="bbars" id="rdrrDdBars"></div>
2164
 
2329
 
-
 
2330
    <div class="sectionlabel">Warehouse Flow — Buying vs Selling <span
-
 
2331
            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>
-
 
2332
    </div>
-
 
2333
    <div id="rdrrDdFlow">
-
 
2334
      <div class="loading">
-
 
2335
        <div class="spinner"></div>
-
 
2336
        Loading&hellip;
-
 
2337
      </div>
-
 
2338
    </div>
-
 
2339
 
2165
    <div class="sectionlabel">Brand Mix — Share of Achievement <span
2340
    <div class="sectionlabel">Brand Mix — Share of Achievement <span
2166
            style="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(click a brand for movement split)</span>
2341
            style="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(click a brand for movement split)</span>
2167
    </div>
2342
    </div>
2168
    <div id="rdrrDdBrands">
2343
    <div id="rdrrDdBrands">
2169
      <div class="loading">
2344
      <div class="loading">
Line 2400... Line 2575...
2400
    }
2575
    }
2401
 
2576
 
2402
    // Cached latest payload so the "Target Status by RBM" modal can render
2577
    // Cached latest payload so the "Target Status by RBM" modal can render
2403
    // without a re-fetch when the KPI card is clicked.
2578
    // without a re-fetch when the KPI card is clicked.
2404
    var LAST_PAYLOAD = null;
2579
    var LAST_PAYLOAD = null;
-
 
2580
    var CURRENT_FILTER_AUTH_ID = "";  // "" = all RBMs
2405
 
2581
 
2406
    function render(data) {
2582
    function render(data) {
2407
      LAST_PAYLOAD = data;
2583
      LAST_PAYLOAD = data;
2408
      var rows = data.rows || [];
2584
      populateRbmFilter(data.rows || []);
-
 
2585
      renderFiltered();
-
 
2586
    }
-
 
2587
 
-
 
2588
    // Build the dropdown options from the fetched rows (unique RBM names).
-
 
2589
    // Preserves current selection if the RBM still exists in the new dataset.
-
 
2590
    function populateRbmFilter(rows) {
-
 
2591
      var seen = {};
-
 
2592
      var options = [];
-
 
2593
      rows.forEach(function (r) {
-
 
2594
        if (!seen[r.authId]) {
-
 
2595
          seen[r.authId] = true;
-
 
2596
          options.push({id: r.authId, name: r.rbmName});
-
 
2597
        }
-
 
2598
      });
-
 
2599
      options.sort(function (a, b) {
-
 
2600
        return (a.name || "").localeCompare(b.name || "");
-
 
2601
      });
-
 
2602
      var sel = $("#rdrrRbmFilter");
2409
      var t = data.totals || {};
2603
      var prev = sel.val() || "";
-
 
2604
      var html = '<option value="">All RBMs (' + options.length + ')</option>';
-
 
2605
      options.forEach(function (o) {
-
 
2606
        html += '<option value="' + o.id + '">' + escapeHtml(o.name || "Unknown") + '</option>';
-
 
2607
      });
-
 
2608
      sel.html(html);
-
 
2609
      // Restore prior selection if still valid; otherwise reset.
-
 
2610
      if (prev && seen[prev]) sel.val(prev); else CURRENT_FILTER_AUTH_ID = "";
-
 
2611
    }
-
 
2612
 
-
 
2613
    // Renders KPIs + grid from LAST_PAYLOAD filtered by CURRENT_FILTER_AUTH_ID.
-
 
2614
    function renderFiltered() {
-
 
2615
      if (!LAST_PAYLOAD) return;
-
 
2616
      var allRows = LAST_PAYLOAD.rows || [];
-
 
2617
      var rows = CURRENT_FILTER_AUTH_ID
-
 
2618
              ? allRows.filter(function (r) {
-
 
2619
                return String(r.authId) === String(CURRENT_FILTER_AUTH_ID);
-
 
2620
              })
-
 
2621
              : allRows;
-
 
2622
 
-
 
2623
      // Recompute totals from the filtered subset. Cheaper + correct vs using server totals.
-
 
2624
      var t = {
-
 
2625
        totalAch: 0, totalTgt: 0, hidAch: 0, hidTgt: 0, fastAch: 0, fastTgt: 0,
-
 
2626
        runAch: 0, runTgt: 0, slowAch: 0, slowTgt: 0, otherAch: 0, otherTgt: 0,
-
 
2627
        rbmsAtOrAbove100: 0, rbmRowCount: rows.length
-
 
2628
      };
-
 
2629
      rows.forEach(function (r) {
-
 
2630
        t.totalAch += Number(r.totalAch) || 0;
-
 
2631
        t.totalTgt += Number(r.totalTgt) || 0;
-
 
2632
        t.hidAch += Number(r.hidAch) || 0;
-
 
2633
        t.hidTgt += Number(r.hidTgt) || 0;
-
 
2634
        t.fastAch += Number(r.fastAch) || 0;
-
 
2635
        t.fastTgt += Number(r.fastTgt) || 0;
-
 
2636
        t.runAch += Number(r.runAch) || 0;
-
 
2637
        t.runTgt += Number(r.runTgt) || 0;
-
 
2638
        t.slowAch += Number(r.slowAch) || 0;
-
 
2639
        t.slowTgt += Number(r.slowTgt) || 0;
-
 
2640
        t.otherAch += Number(r.otherAch) || 0;
-
 
2641
        t.otherTgt += Number(r.otherTgt) || 0;
-
 
2642
        if (r.totalTgt > 0 && r.totalAch >= r.totalTgt) t.rbmsAtOrAbove100++;
-
 
2643
      });
2410
 
2644
 
2411
      $("#rdrrKpiAch").html('&#8377;&nbsp;' + fmtLakh(t.totalAch));
2645
      $("#rdrrKpiAch").html('&#8377;&nbsp;' + fmtLakh(t.totalAch));
2412
      $("#rdrrKpiTgt").html('&#8377;&nbsp;' + fmtLakh(t.totalTgt));
2646
      $("#rdrrKpiTgt").html('&#8377;&nbsp;' + fmtLakh(t.totalTgt));
2413
      var pk = pctLabel(t.totalAch, t.totalTgt);
2647
      var pk = pctLabel(t.totalAch, t.totalTgt);
2414
      $("#rdrrKpiPct").html(pk.cls === "na" ? '<span class="u">n/a</span>' :
2648
      $("#rdrrKpiPct").html(pk.cls === "na" ? '<span class="u">n/a</span>' :
2415
              '<span class="pct ' + pk.cls + '">' + pk.text + '</span>');
2649
              '<span class="pct ' + pk.cls + '">' + pk.text + '</span>');
2416
      $("#rdrrKpiTop").html((t.rbmsAtOrAbove100 || 0) + ' <span class="u">/ ' + (t.rbmRowCount || 0) + '</span>');
2650
      $("#rdrrKpiTop").html((t.rbmsAtOrAbove100 || 0) + ' <span class="u">/ ' + (t.rbmRowCount || 0) + '</span>');
2417
 
2651
 
2418
      if (rows.length === 0) {
2652
      if (rows.length === 0) {
2419
        $("#rdrrTableWrap").html('<div class="empty"><div class="ei">📭</div>No RBM rows in this range.</div>');
2653
        $("#rdrrTableWrap").html('<div class="empty"><div class="ei">📭</div>' +
-
 
2654
                (CURRENT_FILTER_AUTH_ID ? 'This RBM has no rows in the current range.' : 'No RBM rows in this range.') +
-
 
2655
                '</div>');
2420
        return;
2656
        return;
2421
      }
2657
      }
2422
 
2658
 
2423
      var buckets = [
2659
      var buckets = [
2424
        {key: "hid", label: "HID"},
2660
        {key: "hid", label: "HID"},
Line 2504... Line 2740...
2504
        $("#rdrrDdAch").text("—");
2740
        $("#rdrrDdAch").text("—");
2505
        $("#rdrrDdTgt").text("—");
2741
        $("#rdrrDdTgt").text("—");
2506
        $("#rdrrDdPct").text("—");
2742
        $("#rdrrDdPct").text("—");
2507
      }
2743
      }
2508
      $("#rdrrDdBrands").html('<div class="loading"><div class="spinner"></div>Loading brand mix&hellip;</div>');
2744
      $("#rdrrDdBrands").html('<div class="loading"><div class="spinner"></div>Loading brand mix&hellip;</div>');
-
 
2745
      $("#rdrrDdFlow").html('<div class="loading"><div class="spinner"></div>Loading&hellip;</div>');
2509
 
2746
 
2510
      fetch("/rbm-drr/rbm-detail?authId=" + authId + "&warehouseId=" + warehouseId +
2747
      fetch("/rbm-drr/rbm-detail?authId=" + authId + "&warehouseId=" + warehouseId +
2511
              "&startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"})
2748
              "&startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"})
2512
              .then(function (r) {
2749
              .then(function (r) {
2513
                return r.json();
2750
                return r.json();
2514
              })
2751
              })
2515
              .then(function (data) {
2752
              .then(function (data) {
2516
                renderBrands(data);
2753
                renderBrands(data);
-
 
2754
                renderWarehouseFlow(data, cachedRow);
2517
              })
2755
              })
2518
              .catch(function (e) {
2756
              .catch(function (e) {
2519
                $("#rdrrDdBrands").html('<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>');
2757
                var msg = '<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>';
-
 
2758
                $("#rdrrDdBrands").html(msg);
-
 
2759
                $("#rdrrDdFlow").html(msg);
2520
              });
2760
              });
2521
    }
2761
    }
2522
 
2762
 
-
 
2763
    function renderWarehouseFlow(data, row) {
-
 
2764
      var buying = (data && data.buying) || [];
-
 
2765
      // Map buying values by status for O(1) lookup.
-
 
2766
      var buyMap = {};
-
 
2767
      buying.forEach(function (b) {
-
 
2768
        buyMap[b.status] = Number(b.value) || 0;
-
 
2769
      });
-
 
2770
      var labels = {HID: "HID", FASTMOVING: "FAST", RUNNING: "RUN", SLOWMOVING: "SLOW", OTHER: "OTHER"};
-
 
2771
      var order = ["HID", "FASTMOVING", "RUNNING", "SLOWMOVING", "OTHER"];
-
 
2772
      // Selling per bucket from the cached grid row (already has ACH per bucket).
-
 
2773
      var sellMap = {
-
 
2774
        HID: row ? Number(row.hidAch) || 0 : 0,
-
 
2775
        FASTMOVING: row ? Number(row.fastAch) || 0 : 0,
-
 
2776
        RUNNING: row ? Number(row.runAch) || 0 : 0,
-
 
2777
        SLOWMOVING: row ? Number(row.slowAch) || 0 : 0,
-
 
2778
        OTHER: row ? Number(row.otherAch) || 0 : 0
-
 
2779
      };
-
 
2780
      var totBuy = 0, totSell = 0;
-
 
2781
      order.forEach(function (s) {
-
 
2782
        totBuy += buyMap[s] || 0;
-
 
2783
        totSell += sellMap[s] || 0;
-
 
2784
      });
-
 
2785
 
-
 
2786
      // "Coverage" = selling / buying. High coverage = we sold most of what came in.
-
 
2787
      // Low coverage = supply piling up (aging risk).
-
 
2788
      function covCell(sell, buy) {
-
 
2789
        if (!buy || buy <= 0) {
-
 
2790
          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>';
-
 
2791
          return '<span class="cov-cell na">—</span>';
-
 
2792
        }
-
 
2793
        var pct = (sell / buy) * 100;
-
 
2794
        var cls = pct >= 60 ? 'good' : pct >= 25 ? 'warn' : 'bad';
-
 
2795
        var w = Math.min(100, pct);
-
 
2796
        return '<span class="cov-cell ' + cls + '">' +
-
 
2797
                '<span class="cov-bar"><i style="width:' + w.toFixed(1) + '%"></i></span>' +
-
 
2798
                '<span class="cov-pct">' + pct.toFixed(1) + '%</span></span>';
-
 
2799
      }
-
 
2800
 
-
 
2801
      var html = '<table class="flow-tbl"><thead><tr>' +
-
 
2802
              '<th>Movement</th><th class="num">Buying</th><th class="num">Selling</th><th class="num">Coverage (sold / bought)</th>' +
-
 
2803
              '</tr></thead><tbody>';
-
 
2804
      order.forEach(function (s) {
-
 
2805
        var buy = buyMap[s] || 0, sell = sellMap[s] || 0;
-
 
2806
        html += '<tr class="' + s.toLowerCase() + '">' +
-
 
2807
                '<td><i></i>' + labels[s] + '</td>' +
-
 
2808
                '<td class="num">' + fmtLakh(buy) + '</td>' +
-
 
2809
                '<td class="num">' + fmtLakh(sell) + '</td>' +
-
 
2810
                '<td class="num">' + covCell(sell, buy) + '</td>' +
-
 
2811
                '</tr>';
-
 
2812
      });
-
 
2813
      html += '<tr class="tot">' +
-
 
2814
              '<td>Total</td>' +
-
 
2815
              '<td class="num">' + fmtLakh(totBuy) + '</td>' +
-
 
2816
              '<td class="num">' + fmtLakh(totSell) + '</td>' +
-
 
2817
              '<td class="num">' + covCell(totSell, totBuy) + '</td>' +
-
 
2818
              '</tr>';
-
 
2819
      html += '</tbody></table>';
-
 
2820
      $("#rdrrDdFlow").html(html);
-
 
2821
    }
-
 
2822
 
2523
    function renderBrands(data) {
2823
    function renderBrands(data) {
2524
      var brands = (data && data.brands) || [];
2824
      var brands = (data && data.brands) || [];
2525
      if (brands.length === 0) {
2825
      if (brands.length === 0) {
2526
        $("#rdrrDdBrands").html('<div class="empty"><div class="ei">🏷️</div>No brand sales in this range.</div>');
2826
        $("#rdrrDdBrands").html('<div class="empty"><div class="ei">🏷️</div>No brand sales in this range.</div>');
2527
        return;
2827
        return;
Line 2637... Line 2937...
2637
      if (e.target === this) $(this).removeClass("show");
2937
      if (e.target === this) $(this).removeClass("show");
2638
    });
2938
    });
2639
 
2939
 
2640
    function openStatusModal() {
2940
    function openStatusModal() {
2641
      if (!LAST_PAYLOAD) return;
2941
      if (!LAST_PAYLOAD) return;
-
 
2942
      // Respect the RBM filter so the modal matches what's on screen.
2642
      var rows = (LAST_PAYLOAD.rows || []).slice();
2943
      var rows = (LAST_PAYLOAD.rows || []).filter(function (r) {
-
 
2944
        return !CURRENT_FILTER_AUTH_ID || String(r.authId) === String(CURRENT_FILTER_AUTH_ID);
-
 
2945
      });
2643
      var achieved = [], pending = [];
2946
      var achieved = [], pending = [];
2644
      rows.forEach(function (r) {
2947
      rows.forEach(function (r) {
2645
        if (!r.totalTgt || r.totalTgt <= 0) return;
2948
        if (!r.totalTgt || r.totalTgt <= 0) return;
2646
        var pct = (r.totalAch / r.totalTgt) * 100;
2949
        var pct = (r.totalAch / r.totalTgt) * 100;
2647
        var enriched = {row: r, pct: pct};
2950
        var enriched = {row: r, pct: pct};
Line 2907... Line 3210...
2907
    });
3210
    });
2908
    $("#rdrrCallDrawerBack").on("click", function (e) {
3211
    $("#rdrrCallDrawerBack").on("click", function (e) {
2909
      if (e.target === this) $(this).removeClass("show");
3212
      if (e.target === this) $(this).removeClass("show");
2910
    });
3213
    });
2911
 
3214
 
-
 
3215
    // RBM filter — re-render KPIs + grid from the cached payload (no re-fetch).
-
 
3216
    $("#rdrrRbmFilter").on("change", function () {
-
 
3217
      CURRENT_FILTER_AUTH_ID = $(this).val() || "";
-
 
3218
      renderFiltered();
-
 
3219
    });
-
 
3220
 
2912
    // initial load
3221
    // initial load
2913
    load();
3222
    load();
2914
    loadPurchaseMilestones();
3223
    loadPurchaseMilestones();
2915
    loadPerformanceDashboard();
3224
    loadPerformanceDashboard();
2916
    loadCallTargetIframe();
3225
    loadCallTargetIframe();