Rev 37775 | Blame | Compare with Previous | Last modification | View Log | RSS feed
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>RBM DRR Dashboard</title><style>:root {--bg: #f4f6fb;--card: #ffffff;--ink: #141a2b;--muted: #5d6a83;--soft: #97a1b5;--line: #e6e9f1;--line2: #eef1f6;--brand: #3b5bdb;--brand2: #5c7cfa;--teal: #0ca678;--cyan: #1098ad;--amber: #f59f00;--orange: #e8590c;--red: #e03131;--violet: #7048e8;--pink: #d6336c;--good: #2f9e44;--warn: #e8590c;--bad: #e03131;--hid: #7048e8;--fast: #3b5bdb;--run: #0ca678;--slow: #e8590c;--other: #868e96;--hid-bg: #f2edff;--fast-bg: #eaf0ff;--run-bg: #e5f7ee;--slow-bg: #ffefe0;--other-bg: #eef0f4;}* {box-sizing: border-box}html, body {margin: 0;background: var(--bg);color: var(--ink);font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;font-size: 14px}.wrap {max-width: none;margin: 0 auto;padding: 22px 20px 100px}a {color: var(--brand);text-decoration: none}a:hover {text-decoration: underline}/* header */header.top {display: flex;align-items: flex-end;justify-content: space-between;gap: 18px;flex-wrap: wrap;margin-bottom: 20px}.brand .crumb {font-size: 12px;color: var(--soft);font-weight: 600}.brand h1 {margin: 4px 0 0;font-size: 26px;font-weight: 770;letter-spacing: -.015em;display: flex;align-items: center;gap: 12px}.brand h1 .live {font-size: 11px;font-weight: 700;color: var(--teal);background: #e6fcf5;border: 1px solid #c3fae8;border-radius: 999px;padding: 3px 10px}.brand h1 .live i {display: inline-block;width: 7px;height: 7px;border-radius: 50%;background: var(--teal);margin-right: 5px;animation: pulse 1.6s infinite;vertical-align: middle}@keyframes pulse {0%, 100% {opacity: 1}50% {opacity: .25}}.hd-right {display: flex;align-items: center;gap: 10px;flex-wrap: wrap}.ctl {background: var(--card);border: 1px solid var(--line);border-radius: 10px;padding: 9px 13px;font-size: 13px;color: var(--ink);font-weight: 600;box-shadow: 0 1px 2px rgba(20, 30, 60, .04)}input.ctl {appearance: none;cursor: pointer;height: 38px;min-width: 240px}.btn-r {background: var(--brand);border: 0;color: #fff;font-weight: 670;padding: 9px 18px;border-radius: 10px;cursor: pointer;font-size: 13px;height: 38px;box-shadow: 0 1px 2px rgba(59, 91, 219, .35);transition: transform .1s, box-shadow .1s}.btn-r:hover {background: var(--brand2);box-shadow: 0 3px 10px rgba(59, 91, 219, .35)}.btn-r:active {transform: translateY(1px)}/* KPI cards */.kpis {display: grid;grid-template-columns:repeat(4, 1fr);gap: 14px;margin-bottom: 22px}.kpi {position: relative;background: var(--card);border: 1px solid var(--line);border-radius: 14px;padding: 16px 18px;box-shadow: 0 1px 2px rgba(20, 30, 60, .05);display: flex;flex-direction: column;min-height: 118px;overflow: hidden}.kpi::before {content: "";position: absolute;top: 0;left: 0;height: 3px;width: 100%;border-top-left-radius: 14px;border-top-right-radius: 14px}.kpi.a::before {background: linear-gradient(90deg, var(--brand), var(--brand2))}.kpi.b::before {background: linear-gradient(90deg, var(--teal), #20c997)}.kpi.c::before {background: linear-gradient(90deg, var(--amber), #ffc078)}.kpi.d::before {background: linear-gradient(90deg, var(--violet), #9775fa)}.kpi .top {display: flex;align-items: center;gap: 10px}.kpi .ic {width: 34px;height: 34px;border-radius: 10px;display: flex;align-items: center;justify-content: center;flex: none;color: #fff;box-shadow: 0 2px 6px rgba(0, 0, 0, .08)}.kpi.a .ic {background: linear-gradient(135deg, var(--brand), var(--brand2))}.kpi.b .ic {background: linear-gradient(135deg, var(--teal), #20c997)}.kpi.c .ic {background: linear-gradient(135deg, var(--amber), #ffc078);color: #3a2500}.kpi.d .ic {background: linear-gradient(135deg, var(--violet), #9775fa)}.kpi .ic svg {width: 18px;height: 18px}.kpi .label {font-size: 12px;color: var(--muted);font-weight: 650;line-height: 1.2;letter-spacing: .005em}.kpi .val {font-size: 28px;font-weight: 780;margin-top: auto;padding-top: 10px;letter-spacing: -.02em;line-height: 1;font-variant-numeric: tabular-nums}.kpi .val .u {font-size: 13px;color: var(--soft);font-weight: 600;margin-left: 3px}.kpi .val .rdrr-kpi-billed {font-size: 20px;color: var(--soft);font-weight: 700;margin-left: 4px;letter-spacing: -.01em}.kpi .foot {font-size: 11.5px;color: var(--soft);margin-top: 8px;font-weight: 500}.kpi .val .pct {margin-left: 0;font-size: 20px;padding: 4px 12px}.kpi.clickable {cursor: pointer;transition: transform .1s, box-shadow .12s, border-color .12s}.kpi.clickable:hover {transform: translateY(-2px);box-shadow: 0 6px 18px rgba(20, 30, 60, .08);border-color: #c8d2f4}.kpi.clickable .hint {position: absolute;bottom: 8px;right: 12px;color: var(--soft);font-size: 11px;font-weight: 600;opacity: .7}.kpi.clickable:hover .hint {opacity: 1;color: var(--brand)}/* Cards & section labels */.card {background: var(--card);border: 1px solid var(--line);border-radius: 15px;padding: 18px 20px;margin-bottom: 16px;box-shadow: 0 1px 2px rgba(20, 30, 60, .04)}.card .card-hd {display: flex;align-items: baseline;justify-content: space-between;gap: 14px;margin-bottom: 14px}.card h2 {margin: 0;font-size: 16px;font-weight: 730;letter-spacing: -.01em}.card .desc {color: var(--muted);font-size: 12.5px;margin: 2px 0 0}.card .badge-period {background: #eef2ff;border: 1px solid #d7defb;color: var(--brand);border-radius: 999px;padding: 3px 12px;font-size: 12px;font-weight: 650;letter-spacing: .01em}.sectionlabel {font-size: 11px;letter-spacing: .14em;text-transform: uppercase;color: var(--soft);font-weight: 700;margin: 20px 2px 10px}/* Legend under section label */.legend {display: flex;gap: 14px;flex-wrap: wrap;margin: 0 2px 10px;align-items: center;color: var(--soft);font-size: 11.5px;font-weight: 600}.legend .lg {display: inline-flex;align-items: center;gap: 5px}.legend .lg i {width: 8px;height: 8px;border-radius: 50%;display: inline-block}.legend .lg.hid i {background: var(--hid)}.legend .lg.fast i {background: var(--fast)}.legend .lg.run i {background: var(--run)}.legend .lg.slow i {background: var(--slow)}.legend .lg.other i {background: var(--other)}.legend .lg .lgt {color: var(--muted)}/* Table */table {width: 100%;border-collapse: separate;border-spacing: 0;font-size: 13px}th, td {text-align: left;padding: 11px 10px;border-bottom: 1px solid var(--line2)}thead th {font-size: 10.5px;text-transform: uppercase;letter-spacing: .06em;color: var(--soft);font-weight: 700;white-space: nowrap;user-select: none;position: sticky;top: 0;background: #fff;z-index: 1;border-bottom: 1px solid var(--line)}th.num, td.num {text-align: right}th.g-hid {color: var(--hid);border-top: 3px solid var(--hid)}th.g-fast {color: var(--fast);border-top: 3px solid var(--fast)}th.g-run {color: var(--run);border-top: 3px solid var(--run)}th.g-slow {color: var(--slow);border-top: 3px solid var(--slow)}th.g-other {color: var(--other);border-top: 3px solid var(--other)}th.g-total {color: var(--brand);border-top: 3px solid var(--brand)}tbody tr:hover {background: #f6f8fd}tbody tr.clickable {cursor: pointer;transition: background .1s}tbody tr.clickable:hover {background: #eef2ff}tr.totalrow {background: #fafbfe;border-top: 2px solid var(--line)}tr.totalrow td {font-weight: 770;color: var(--ink)}tr.totalrow .cell-val {font-size: 14px}.rbm-cell {display: flex;flex-direction: column;gap: 2px}.rbm-cell .rn {font-weight: 700;color: var(--ink);font-size: 13.5px}.rbm-cell .wh {font-size: 11.5px;color: var(--muted);font-weight: 600}/* Top-level "All RBMs" collapsed row */#rdrrGrid tr.top-parent {cursor: pointer;background: linear-gradient(180deg, #eef2fb, #dfe6f5);font-weight: 700}#rdrrGrid tr.top-parent:hover {background: linear-gradient(180deg, #dfe6f5, #cfd9ee)}#rdrrGrid tr.top-parent td {border-top: 2px solid var(--brand);border-bottom: 2px solid var(--brand)}#rdrrGrid tr.top-parent .top-toggle {display: inline-block;transition: transform .15s ease;font-weight: 800;color: var(--brand);margin-right: 4px;font-size: 15px}#rdrrGrid tr.top-parent .cell-val {font-weight: 800;color: var(--brand)}/* Multi-warehouse collapse — RBM-level parent row */#rdrrGrid tr.grp-parent {cursor: pointer;background: #fafbfe}#rdrrGrid tr.grp-parent:hover {background: #eef2fb}#rdrrGrid tr.grp-parent .grp-toggle {display: inline-block;transition: transform .15s ease;font-weight: 700;color: var(--brand);margin-right: 2px}#rdrrGrid tr.grp-child td {background: #fdfdff}.cell-val {font-variant-numeric: tabular-nums;font-weight: 650;color: var(--ink);white-space: nowrap}.cell-val small {color: var(--soft);font-weight: 500;margin-left: 3px}.pct {display: inline-flex;align-items: center;justify-content: center;min-width: 56px;padding: 3px 9px;border-radius: 7px;font-size: 11.5px;font-weight: 700;text-align: center;font-variant-numeric: tabular-nums;line-height: 1.35}.pct.good {background: #e5f7ee;color: #177a3a}.pct.warn {background: #fff0e0;color: #a04600}.pct.bad {background: #ffeaea;color: #b8272b}.pct.na {background: transparent;color: var(--soft);font-weight: 600;min-width: auto;padding: 0}.bar {height: 5px;border-radius: 3px;background: #eef1f6;overflow: hidden;margin-top: 6px}.bar > i {display: block;height: 100%;border-radius: 3px;transition: width .3s ease}.bar > i.good {background: linear-gradient(90deg, #51cf66, #2f9e44)}.bar > i.warn {background: linear-gradient(90deg, #ffa94d, #e8590c)}.bar > i.bad {background: linear-gradient(90deg, #ff8787, #e03131)}.chev {color: #c5cce0;font-size: 16px;padding-left: 6px;transition: transform .12s, color .12s}tr.clickable:hover .chev {color: var(--brand);transform: translateX(2px)}.loading {padding: 70px;text-align: center;color: var(--muted)}.spinner {width: 32px;height: 32px;border: 3px solid #e3e8f5;border-top-color: var(--brand);border-radius: 50%;animation: spin 1s linear infinite;margin: 0 auto 12px}@keyframes spin {to {transform: rotate(360deg)}}.empty {padding: 56px 20px;text-align: center;color: var(--soft);font-size: 13.5px}.empty .ei {font-size: 36px;margin-bottom: 8px;opacity: .5}.foot-note {color: var(--soft);font-size: 11.5px;margin-top: 14px;padding: 10px 12px;background: #fafbfe;border: 1px dashed var(--line);border-radius: 10px;line-height: 1.5}.foot-note code {background: #eef1f6;color: var(--muted);padding: 1px 5px;border-radius: 4px;font-size: 11px}/* drill-down panel */.rdrr-back {position: fixed;inset: 0;background: rgba(15, 22, 40, .5);backdrop-filter: blur(2px);z-index: 1200;display: none}.rdrr-back.show {display: block}.rdrr-panel {position: absolute;top: 0;right: 0;height: 100%;width: min(760px, 96vw);background: #fff;box-shadow: -12px 0 40px rgba(20, 30, 60, .22);display: flex;flex-direction: column;padding: 24px 26px 20px;transform: translateX(100%);transition: transform .22s ease;overflow-y: auto}.rdrr-back.show .rdrr-panel {transform: translateX(0)}.rdrr-panel .x {position: absolute;top: 16px;right: 18px;cursor: pointer;color: var(--soft);font-size: 24px;line-height: 1;border: 0;background: none;width: 32px;height: 32px;border-radius: 8px;transition: background .1s, color .1s}.rdrr-panel .x:hover {background: #f1f3f9;color: var(--ink)}.rdrr-panel h3 {margin: 0 0 3px;font-size: 19px;font-weight: 750;letter-spacing: -.01em;padding-right: 40px}.rdrr-panel > .desc {margin: 0 0 18px;font-size: 12.5px;color: var(--muted);font-weight: 600}.rdrr-total {display: flex;gap: 14px;padding: 14px 16px;background: linear-gradient(135deg, #f8f9fc, #eef2ff);border: 1px solid #dbe2f6;border-radius: 12px;margin-bottom: 18px}.rdrr-total > div {flex: 1;text-align: center;border-right: 1px solid #dbe2f6;padding: 0 4px}.rdrr-total > div:last-child {border-right: 0}.rdrr-total .k {font-size: 10.5px;color: var(--muted);text-transform: uppercase;letter-spacing: .06em;font-weight: 700}.rdrr-total .v {font-size: 20px;font-weight: 770;color: var(--ink);font-variant-numeric: tabular-nums;margin-top: 5px;letter-spacing: -.01em}.rdrr-total .v .pct {font-size: 16px;padding: 3px 10px;margin-top: 0}.bbars {display: flex;flex-direction: column;gap: 10px;margin-bottom: 20px}.bbar {display: grid;grid-template-columns:82px 1fr 145px 60px;gap: 14px;align-items: center;padding: 12px 14px;background: #fff;border: 1px solid var(--line);border-radius: 11px;transition: box-shadow .12s}.bbar:hover {box-shadow: 0 3px 10px rgba(20, 30, 60, .06)}.bbar .bname {font-size: 11.5px;font-weight: 750;color: var(--ink);letter-spacing: .04em;text-transform: uppercase;display: flex;align-items: center;gap: 6px}.bbar .bname i {display: inline-block;width: 8px;height: 8px;border-radius: 50%}.bbar.hid .bname {color: var(--hid)}.bbar.fast .bname {color: var(--fast)}.bbar.run .bname {color: var(--run)}.bbar.slow .bname {color: var(--slow)}.bbar.other .bname {color: var(--other)}.bbar.hid .bname i {background: var(--hid)}.bbar.fast .bname i {background: var(--fast)}.bbar.run .bname i {background: var(--run)}.bbar.slow .bname i {background: var(--slow)}.bbar.other .bname i {background: var(--other)}.bbar .btrack {height: 14px;border-radius: 8px;overflow: hidden;position: relative}.bbar.hid .btrack {background: var(--hid-bg)}.bbar.fast .btrack {background: var(--fast-bg)}.bbar.run .btrack {background: var(--run-bg)}.bbar.slow .btrack {background: var(--slow-bg)}.bbar.other .btrack {background: var(--other-bg)}.bbar .btrack > i {display: block;height: 100%;border-radius: 8px;transition: width .3s ease;position: relative}.bbar.hid .btrack > i {background: linear-gradient(90deg, #9775fa, var(--hid))}.bbar.fast .btrack > i {background: linear-gradient(90deg, var(--brand2), var(--fast))}.bbar.run .btrack > i {background: linear-gradient(90deg, #20c997, var(--run))}.bbar.slow .btrack > i {background: linear-gradient(90deg, #ffa94d, var(--slow))}.bbar.other .btrack > i {background: linear-gradient(90deg, #adb5bd, var(--other))}.bbar .btgt {font-size: 12px;color: var(--muted);font-variant-numeric: tabular-nums;font-weight: 600;text-align: right}.bbar .btgt b {color: var(--ink);font-weight: 700}.bbar .bpct {font-size: 13px;font-weight: 750;text-align: right;font-variant-numeric: tabular-nums}.bbar .bpct.good {color: var(--good)}.bbar .bpct.warn {color: var(--warn)}.bbar .bpct.bad {color: var(--bad)}.bbar .bpct.na {color: var(--soft);font-weight: 600}/* warehouse flow (buying vs selling) in drill-down */.flow-tbl {width: 100%;border-collapse: separate;border-spacing: 0;font-size: 12.5px;margin-bottom: 20px;background: #fff;border: 1px solid var(--line);border-radius: 11px;overflow: hidden}.flow-tbl th, .flow-tbl td {padding: 10px 12px;border-bottom: 1px solid var(--line2);text-align: right;font-variant-numeric: tabular-nums}.flow-tbl th {font-size: 10.5px;text-transform: uppercase;letter-spacing: .05em;color: var(--soft);font-weight: 700;background: #fafbfe;text-align: left;padding-left: 14px}.flow-tbl th.num, .flow-tbl td.num {text-align: right}.flow-tbl thead th:first-child {text-align: left}.flow-tbl tbody tr td:first-child {text-align: left;padding-left: 14px;font-weight: 700;letter-spacing: .03em;text-transform: uppercase;font-size: 11px}.flow-tbl tbody tr td:first-child i {display: inline-block;width: 7px;height: 7px;border-radius: 50%;margin-right: 6px;vertical-align: middle}.flow-tbl tbody tr:last-child td {border-bottom: 0}.flow-tbl tbody tr.hid td:first-child {color: var(--hid)}.flow-tbl tbody tr.hid td:first-child i {background: var(--hid)}.flow-tbl tbody tr.fastmoving td:first-child {color: var(--fast)}.flow-tbl tbody tr.fastmoving td:first-child i {background: var(--fast)}.flow-tbl tbody tr.running td:first-child {color: var(--run)}.flow-tbl tbody tr.running td:first-child i {background: var(--run)}.flow-tbl tbody tr.slowmoving td:first-child {color: var(--slow)}.flow-tbl tbody tr.slowmoving td:first-child i {background: var(--slow)}.flow-tbl tbody tr.other td:first-child {color: var(--other)}.flow-tbl tbody tr.other td:first-child i {background: var(--other)}.flow-tbl .cov-cell {display: inline-flex;align-items: center;gap: 6px;justify-content: flex-end}.flow-tbl .cov-cell .cov-bar {width: 60px;height: 6px;border-radius: 3px;background: #eef1f6;overflow: hidden;flex: none}.flow-tbl .cov-cell .cov-bar > i {display: block;height: 100%;border-radius: 3px}.flow-tbl .cov-cell.good .cov-bar > i {background: var(--good)}.flow-tbl .cov-cell.warn .cov-bar > i {background: var(--warn)}.flow-tbl .cov-cell.bad .cov-bar > i {background: var(--bad)}.flow-tbl .cov-cell.good .cov-pct {color: var(--good);font-weight: 700}.flow-tbl .cov-cell.warn .cov-pct {color: var(--warn);font-weight: 700}.flow-tbl .cov-cell.bad .cov-pct {color: var(--bad);font-weight: 700}.flow-tbl .cov-cell.na {color: var(--soft);font-weight: 500}.flow-tbl tr.tot td {font-weight: 770;background: #fafbfe;color: var(--ink);border-top: 2px solid var(--line)}/* brand mix bars in drill-down */.brands {display: flex;flex-direction: column;gap: 8px;margin-bottom: 20px}.brand-card {background: #fff;border: 1px solid var(--line);border-radius: 11px;overflow: hidden;transition: box-shadow .12s}.brand-card:hover {box-shadow: 0 3px 10px rgba(20, 30, 60, .06)}.brand-card.open {box-shadow: 0 4px 14px rgba(59, 91, 219, .14);border-color: #c8d2f4}.brand-head {display: grid;grid-template-columns:24px 130px 1fr 96px 100px 60px;gap: 12px;align-items: center;padding: 10px 14px;cursor: pointer;user-select: none}.brand-head .bpc {display: inline-flex;align-items: center;justify-content: center;gap: 4px;font-size: 11.5px;font-weight: 700;color: var(--brand);background: #eef2ff;border: 1px solid #d7defb;border-radius: 999px;padding: 3px 9px;font-variant-numeric: tabular-nums;white-space: nowrap;justify-self: center}.brand-head .bpc.zero {color: var(--soft);background: #f1f3f9;border-color: var(--line)}.brand-head .bpc svg {width: 11px;height: 11px}.brand-head .cx {color: #c5cce0;font-size: 14px;transition: transform .18s ease, color .12s}.brand-card.open .brand-head .cx {transform: rotate(90deg);color: var(--brand)}.brand-head .bn {font-size: 13px;font-weight: 700;color: var(--ink);white-space: nowrap;overflow: hidden;text-overflow: ellipsis}.brand-head .btrack {height: 10px;background: #eef1f6;border-radius: 6px;overflow: hidden}.brand-head .btrack > i {display: block;height: 100%;border-radius: 6px;background: linear-gradient(90deg, var(--brand2), var(--brand));transition: width .3s ease}.brand-head .bval {font-size: 13px;font-weight: 700;color: var(--ink);font-variant-numeric: tabular-nums;text-align: right}.brand-head .bshare {font-size: 12px;font-weight: 700;color: var(--muted);font-variant-numeric: tabular-nums;text-align: right}.brand-card.top .brand-head .btrack > i {background: linear-gradient(90deg, #38b2ac, var(--teal))}.brand-card.top .brand-head .bshare {color: var(--teal)}.brand-body {display: none;padding: 6px 14px 14px;border-top: 1px dashed var(--line);background: #fafbfe}.brand-card.open .brand-body {display: block}.brand-body .bhdr {font-size: 10.5px;letter-spacing: .06em;text-transform: uppercase;color: var(--soft);font-weight: 700;margin: 8px 0 8px}.mbars {display: flex;flex-direction: column;gap: 6px}.mbar {display: grid;grid-template-columns:90px 1fr 100px 60px;gap: 10px;align-items: center;padding: 6px 10px;background: #fff;border: 1px solid var(--line2);border-radius: 8px}.mbar .mname {font-size: 11px;font-weight: 700;letter-spacing: .04em;text-transform: uppercase;display: flex;align-items: center;gap: 5px}.mbar .mname i {display: inline-block;width: 7px;height: 7px;border-radius: 50%}.mbar.hid .mname {color: var(--hid)}.mbar.hid .mname i {background: var(--hid)}.mbar.fastmoving .mname {color: var(--fast)}.mbar.fastmoving .mname i {background: var(--fast)}.mbar.running .mname {color: var(--run)}.mbar.running .mname i {background: var(--run)}.mbar.slowmoving .mname {color: var(--slow)}.mbar.slowmoving .mname i {background: var(--slow)}.mbar.other .mname {color: var(--other)}.mbar.other .mname i {background: var(--other)}.mbar .mtrack {height: 10px;border-radius: 5px;overflow: hidden}.mbar.hid .mtrack {background: var(--hid-bg)}.mbar.fastmoving .mtrack {background: var(--fast-bg)}.mbar.running .mtrack {background: var(--run-bg)}.mbar.slowmoving .mtrack {background: var(--slow-bg)}.mbar.other .mtrack {background: var(--other-bg)}.mbar .mtrack > i {display: block;height: 100%;border-radius: 5px;transition: width .3s ease}.mbar.hid .mtrack > i {background: linear-gradient(90deg, #9775fa, var(--hid))}.mbar.fastmoving .mtrack > i {background: linear-gradient(90deg, var(--brand2), var(--fast))}.mbar.running .mtrack > i {background: linear-gradient(90deg, #20c997, var(--run))}.mbar.slowmoving .mtrack > i {background: linear-gradient(90deg, #ffa94d, var(--slow))}.mbar.other .mtrack > i {background: linear-gradient(90deg, #adb5bd, var(--other))}.mbar .mval {font-size: 12px;font-weight: 650;color: var(--ink);font-variant-numeric: tabular-nums;text-align: right}.mbar .mshare {font-size: 12px;font-weight: 700;font-variant-numeric: tabular-nums;text-align: right;color: var(--muted)}/* status list modal (achieved vs pending) */.status-back {position: fixed;inset: 0;background: rgba(15, 22, 40, .5);backdrop-filter: blur(2px);z-index: 1300;display: none;align-items: flex-start;justify-content: center;padding: 60px 20px;overflow-y: auto}.status-back.show {display: flex}.status-modal {background: #fff;border-radius: 16px;max-width: 1000px;width: 100%;box-shadow: 0 20px 60px rgba(20, 30, 60, .28);padding: 22px 24px 20px;position: relative}.status-modal .x {position: absolute;top: 14px;right: 16px;cursor: pointer;color: var(--soft);font-size: 24px;line-height: 1;border: 0;background: none;width: 32px;height: 32px;border-radius: 8px;transition: background .1s}.status-modal .x:hover {background: #f1f3f9;color: var(--ink)}.status-modal h3 {margin: 0 0 3px;font-size: 19px;font-weight: 760;letter-spacing: -.01em;padding-right: 40px}.status-modal > .desc {margin: 0 0 18px;font-size: 12.5px;color: var(--muted);font-weight: 600}.status-grid {display: grid;grid-template-columns:1fr 1fr;gap: 18px}.status-col {border: 1px solid var(--line);border-radius: 12px;padding: 14px 16px;background: #fbfcfe}.status-col.good {border-color: #c3e8d0;background: #f4fbf6}.status-col.bad {border-color: #f2c4c4;background: #fdf5f5}.status-col h4 {margin: 0 0 10px;font-size: 13px;font-weight: 730;display: flex;align-items: center;gap: 8px;letter-spacing: -.005em}.status-col h4 .cnt {background: #fff;border: 1px solid var(--line);color: var(--ink);font-size: 11px;font-weight: 700;padding: 2px 9px;border-radius: 999px}.status-col.good h4 {color: #177a3a}.status-col.good h4 .cnt {background: #e5f7ee;border-color: #c3e8d0;color: #177a3a}.status-col.bad h4 {color: #b8272b}.status-col.bad h4 .cnt {background: #ffeaea;border-color: #f2c4c4;color: #b8272b}.status-list {display: flex;flex-direction: column;gap: 6px;max-height: 60vh;overflow-y: auto;padding-right: 4px}.status-item {display: grid;grid-template-columns:1fr 100px 68px;gap: 10px;align-items: center;padding: 9px 12px;background: #fff;border: 1px solid var(--line2);border-radius: 9px;transition: box-shadow .12s}.status-item:hover {box-shadow: 0 2px 8px rgba(20, 30, 60, .06)}.status-item .rn {font-weight: 700;color: var(--ink);font-size: 12.5px;line-height: 1.3}.status-item .wh {font-size: 11px;color: var(--muted);font-weight: 600;display: block;margin-top: 2px}.status-item .vv {font-size: 12px;color: var(--muted);font-variant-numeric: tabular-nums;font-weight: 600;text-align: right;white-space: nowrap}.status-item .vv b {color: var(--ink);font-weight: 700}.status-item .pp {font-size: 12px;font-weight: 700;font-variant-numeric: tabular-nums;text-align: right}.status-item .pp.good {color: var(--good)}.status-item .pp.warn {color: var(--warn)}.status-item .pp.bad {color: var(--bad)}.status-empty {padding: 24px;text-align: center;color: var(--soft);font-size: 12.5px}/* extras: aging + partner-billing tables */.xtable {width: 100%;border-collapse: separate;border-spacing: 0;font-size: 13px}.xtable th {font-size: 10.5px;text-transform: uppercase;letter-spacing: .06em;color: var(--soft);font-weight: 700;white-space: nowrap;padding: 10px 10px;border-bottom: 1px solid var(--line);text-align: left;background: #fff}.xtable th.num {text-align: right}.xtable td {padding: 10px;border-bottom: 1px solid var(--line2);color: var(--ink);font-weight: 500}.xtable td.num {text-align: right;font-variant-numeric: tabular-nums;font-weight: 650}.xtable tbody tr:hover {background: #f6f8fd}.xtable .rbm-nm {font-weight: 700;color: var(--ink)}.xtable tr.tot td {font-weight: 770;background: #fafbfe;border-top: 2px solid var(--line);color: var(--ink)}.xtable tr.bal td {font-weight: 700;background: #fffbf2;border-top: 1px dashed #f0d68a;color: #7a5a00}.xtable tr.bal td.hdr {font-weight: 770}.xtable td.g-hid {border-left: 2px solid transparent}/* placeholder for future column tint */.xtable th.g-hid, .xtable td.g-hid {color: var(--hid)}.xtable th.g-fast, .xtable td.g-fast {color: var(--fast)}.xtable th.g-run, .xtable td.g-run {color: var(--run)}.xtable th.g-slow, .xtable td.g-slow {color: var(--slow)}.xtable th.g-other, .xtable td.g-other {color: var(--other)}.xtable th.g-total, .xtable td.g-total {color: var(--brand);font-weight: 700}.xtable td.g-hid, .xtable td.g-fast, .xtable td.g-run, .xtable td.g-slow, .xtable td.g-other {opacity: .95}.xtable td.zero {color: var(--soft);font-weight: 500}.xtable .cnt-pill {display: inline-block;min-width: 32px;padding: 2px 8px;border-radius: 6px;font-size: 11.5px;font-weight: 700;font-variant-numeric: tabular-nums;background: #eef2ff;color: var(--brand)}.xtable .cnt-pill.zero {background: #f1f3f9;color: var(--soft)}.xtable .cnt-pill.warn {background: #fff0e0;color: #a04600}.xtable .cnt-pill.good {background: #e5f7ee;color: #177a3a}/* call-target drawer content (Called Partners / OOS Calls) */#rdrrCallDrawerBody {overflow-x: auto}#rdrrCallDrawerBody table {width: 100%;border-collapse: separate;border-spacing: 0;font-size: 12.5px;background: #fff;margin: 0}#rdrrCallDrawerBody table th {font-size: 10.5px;text-transform: uppercase;letter-spacing: .04em;color: var(--soft);font-weight: 700;padding: 9px 10px;background: #fafbfe;border-bottom: 1px solid var(--line);text-align: left;white-space: nowrap}#rdrrCallDrawerBody table td {padding: 9px 10px;border-bottom: 1px solid var(--line2);color: var(--ink);font-weight: 500;vertical-align: middle}#rdrrCallDrawerBody table tr:hover {background: #f6f8fd}#rdrrCallDrawerBody audio {max-width: 170px;height: 26px}/* rbm performance dashboard (extracted from today_po_rbm) */#rdrrPerfWrap {overflow-x: auto}#rdrrPerfWrap table {width: 100%;border-collapse: separate;border-spacing: 0;font-size: 12.5px;background: #fff}#rdrrPerfWrap table th, #rdrrPerfWrap table td {padding: 9px 10px;text-align: center;vertical-align: middle;border-bottom: 1px solid var(--line2);color: var(--ink);font-weight: 500}#rdrrPerfWrap table thead th {font-size: 10.5px;text-transform: uppercase;letter-spacing: .05em;font-weight: 700;background: #1d2433;color: #fff;border-bottom: 0;padding: 10px 8px;line-height: 1.2}#rdrrPerfWrap table thead th small {font-size: 9.5px;color: rgba(255, 255, 255, .7);font-weight: 600;display: block;margin-top: 2px}/* per-column accent badges (keep original palette from today_po_rbm) */#rdrrPerfWrap table thead th:nth-child(4) {background: linear-gradient(180deg, #40c057, #2f9e44)}#rdrrPerfWrap table thead th:nth-child(5) {background: linear-gradient(180deg, #22b8cf, #0e959f)}#rdrrPerfWrap table thead th:nth-child(6) {background: linear-gradient(180deg, #ffa94d, #e8590c)}#rdrrPerfWrap table thead th:nth-child(7) {background: linear-gradient(180deg, #9775fa, #7048e8)}#rdrrPerfWrap table thead th:nth-child(8) {background: linear-gradient(180deg, #ffd43b, #e8ac00);color: #3a2500}#rdrrPerfWrap table thead th:nth-child(8) small {color: rgba(58, 37, 0, .75)}#rdrrPerfWrap table thead th:nth-child(9) {background: linear-gradient(180deg, #ff922b, #e8590c)}#rdrrPerfWrap table thead th:last-child {background: linear-gradient(180deg, #ff6b6b, #e03131)}#rdrrPerfWrap table tbody tr:hover {background: #f6f8fd}/* rank rows keep their original background tint from today_po_rbm inline style */#rdrrPerfWrap table tbody tr td:nth-child(2) {text-align: left;font-weight: 700;color: var(--ink)}#rdrrPerfWrap table tbody tr td:first-child img {width: 24px;height: 24px}#rdrrPerfWrap table tbody tr:first-child td {font-weight: 700}#rdrrPerfWrap table tbody tr td:last-child {font-weight: 770;font-size: 13.5px;font-variant-numeric: tabular-nums}/* purchase-milestones (brand × person) — compact heatmap grid, custom-rendered client-side */#rdrrPmWrap {overflow: hidden}#rdrrPmWrap .pm-grid-wrap {overflow: auto;max-height: 720px;border: 1px solid var(--line);border-radius: 10px;background: #fff}#rdrrPmWrap .pm-grid {border-collapse: separate;border-spacing: 0;font-size: 13px;background: #fff}/* --- header (brand strip) --- */#rdrrPmWrap .pm-grid thead th {position: sticky;top: 0;z-index: 3;padding: 10px 14px;color: #fff;font-weight: 700;letter-spacing: .05em;font-size: 11px;text-transform: uppercase;text-align: center;white-space: nowrap;background: linear-gradient(180deg, var(--brand2), var(--brand));border-bottom: 2px solid rgba(0, 0, 0, .08)}#rdrrPmWrap .pm-grid thead th.pm-corner {position: sticky;left: 0;z-index: 5;background: #fff;color: var(--ink);text-align: left;border-right: 1px solid var(--line);padding: 10px 14px}#rdrrPmWrap .pm-corner-lbl {font-size: 11.5px;font-weight: 700;letter-spacing: .05em;color: var(--ink)}#rdrrPmWrap .pm-corner-hint {font-size: 10px;color: var(--soft);font-weight: 500;letter-spacing: .02em;text-transform: none;margin-top: 1px}/* --- name column --- */#rdrrPmWrap .pm-name {position: sticky;left: 0;z-index: 2;background: #fff;padding: 7px 5px;border-right: 1px solid var(--line);vertical-align: middle}#rdrrPmWrap .pm-name-txt {font-weight: 650;font-size: 16px;color: var(--ink);line-height: 1.2;white-space: nowrap;overflow: hidden;text-overflow: ellipsis}#rdrrPmWrap .pm-name-sub {font-size: 9.5px;color: var(--soft);margin-top: 1px;letter-spacing: .04em;text-transform: uppercase;font-weight: 600}/* --- data cells --- */#rdrrPmWrap .pm-cell {padding: 5px 7px;text-align: left;vertical-align: middle;font-variant-numeric: tabular-nums;color: var(--ink)}#rdrrPmWrap .pm-cell-zero {color: #cbd0d9;font-size: 17px;font-weight: 500;text-align: center}/* Stacked cell content: partner count on top, PO in middle, Billed at bottom (primary) */#rdrrPmWrap .pm-partners {font-size: 12px;font-weight: 700;text-align: right;color: #000;line-height: 1.2;margin-bottom: 4px;white-space: nowrap}#rdrrPmWrap .pm-po {font-size: 14px;font-weight: 600;color: #000;line-height: 1.2;margin-bottom: 4px;white-space: nowrap}#rdrrPmWrap .pm-billed {font-weight: 800;font-size: 16px;line-height: 1.2;color: var(--ink);white-space: nowrap}#rdrrPmWrap .pm-tag {font-size: 12px;font-weight: 500;opacity: .6;margin-left: 2px;letter-spacing: .02em;color: #000;}/* --- row hover --- */#rdrrPmWrap .pm-grid tbody tr.pm-row + tr.pm-row td {border-top: 1px solid var(--line2)}#rdrrPmWrap .pm-grid tbody tr.pm-row:hover .pm-name {background: #eef2fb}/* --- totals row (pinned at top, distinct) --- */#rdrrPmWrap .pm-total-row .pm-name {background: linear-gradient(180deg, #f2f5fb, #eaeff9);border-right: 1px solid var(--line);border-bottom: 2px solid var(--brand)}#rdrrPmWrap .pm-total-row .pm-name-txt {color: var(--brand);font-weight: 800}#rdrrPmWrap .pm-total-row .pm-name-sub {color: var(--brand);opacity: .8}#rdrrPmWrap .pm-cell-total {background: linear-gradient(180deg, #f2f5fb, #eaeff9) !important;border-bottom: 2px solid var(--brand)}#rdrrPmWrap .pm-cell-total .pm-billed {color: var(--brand);font-weight: 800;font-size: 13px}/* --- brand accent header gradients (slug matches JS output) --- */#rdrrPmWrap .pm-grid thead th.total {background: linear-gradient(180deg, #3b5bdb, #28449e)}#rdrrPmWrap .pm-grid thead th.vivo {background: linear-gradient(180deg, #00baf2, #0a8fb5)}#rdrrPmWrap .pm-grid thead th.oppo {background: linear-gradient(180deg, #20a558, #177a3a)}#rdrrPmWrap .pm-grid thead th.samsung {background: linear-gradient(180deg, #0b3d91, #071e51)}#rdrrPmWrap .pm-grid thead th.alcatel {background: linear-gradient(180deg, #5c7cfa, #3b5bdb)}#rdrrPmWrap .pm-grid thead th.motorola {background: linear-gradient(180deg, #495057, #212529)}#rdrrPmWrap .pm-grid thead th.hmd {background: linear-gradient(180deg, #7048e8, #5b34c9)}#rdrrPmWrap .pm-grid thead th.xiaomi {background: linear-gradient(180deg, #ffa726, #e8590c)}#rdrrPmWrap .pm-grid thead th.lava {background: linear-gradient(180deg, #e03131, #a11515)}#rdrrPmWrap .pm-grid thead th.poco {background: linear-gradient(180deg, #ffd43b, #e8ac00);color: #3a2500}#rdrrPmWrap .pm-grid thead th.realme {background: linear-gradient(180deg, #f59f00, #e8590c)}#rdrrPmWrap .pm-grid thead th.apple {background: linear-gradient(180deg, #495057, #141a2b)}#rdrrPmWrap .pm-grid thead th.oneplus {background: linear-gradient(180deg, #e03131, #a11515)}#rdrrPmWrap .pm-grid thead th.tecno {background: linear-gradient(180deg, #0ca678, #087a56)}@media (max-width: 960px) {.kpis {grid-template-columns:repeat(2, 1fr)}.brand h1 {font-size: 22px}.status-grid {grid-template-columns:1fr}}</style><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css"/></head><body><div class="wrap"><header class="top"><div class="brand"><div class="crumb"><a href="/indent/today_po_rbm">← Today PO / RBM</a> · Sales · Indent</div><h1>RBM DRR Dashboard <span class="live"><i></i>Live</span></h1></div><div class="hd-right"><select id="rdrrTypeSelect" class="ctl" style="min-width:120px;height:38px;padding:8px 12px" title="Filter type"><option value="RBM" selected>RBM</option><option value="SALES">Sales</option></select><select id="rdrrLevelSelect" class="ctl" style="min-width:140px;height:38px;padding:8px 12px;display:none"title="Sales level"><option value="">Select level</option><option value="ASM">ASM (L1)</option><option value="CM">CM (L2)</option><option value="DM">DM (L3)</option><option value="BM">BM (L4)</option><option value="RSM">RSM (L5)</option><option value="NSM">NSM (L6)</option></select><select id="rdrrPersonSelect" class="ctl" style="min-width:230px;height:38px;padding:8px 12px" title="Person"><option value="">All RBMs</option></select><input class="ctl" id="rdrrPeriod" type="text" placeholder="Select date range"/><button class="btn-r" id="rdrrApply">Apply</button></div></header><div class="kpis"><div class="kpi a"><div class="top"><div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"stroke-linejoin="round"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg></div><div class="label">Total Achieved</div></div><div class="val" id="rdrrKpiAch">—</div><div class="foot" id="rdrrKpiAchSub">PO value / Actually billed (invoiced)</div></div><div class="kpi b"><div class="top"><div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></svg></div><div class="label">Total Target</div></div><div class="val" id="rdrrKpiTgt">—</div><div class="foot" id="rdrrKpiTgtSub">Sum of daily target snapshots</div></div><div class="kpi c"><div class="top"><div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"stroke-linejoin="round"><path d="M3 17l6-6 4 4 8-8"/><path d="M17 7h4v4"/></svg></div><div class="label">Achievement %</div></div><div class="val" id="rdrrKpiPct">—</div><div class="foot" id="rdrrKpiPctSub">Total ACH / Total TGT</div></div><div class="kpi d clickable" id="rdrrKpiTopCard" title="Click to see achieved vs pending RBMs"><div class="top"><div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"stroke-linejoin="round"><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"/></svg></div><div class="label">RBMs ≥ 100% Target</div></div><div class="val" id="rdrrKpiTop">—</div><div class="foot" id="rdrrKpiTopSub">Rows meeting or beating target</div><span class="hint">Click for list ›</span></div></div><div class="sectionlabel">RBM × Warehouse × Movement Bucket</div><div class="legend"><span class="lg hid"><i></i><span class="lgt">HID</span></span><span class="lg fast"><i></i><span class="lgt">FAST</span></span><span class="lg run"><i></i><span class="lgt">RUN</span></span><span class="lg slow"><i></i><span class="lgt">SLOW</span></span><span class="lg other"><i></i><span class="lgt">OTHER</span></span></div><div class="card"><div class="card-hd"><div><h2>Achievement vs Target</h2><p class="desc">Click any row for a per-bucket breakdown and the catalogs sold.</p></div><span class="badge-period" id="rdrrPeriodLabel">—</span></div><div id="rdrrTableWrap"><div class="loading"><div class="spinner"></div>Loading…</div></div><div class="foot-note"><b>TGT</b> is summed from persisted per-day snapshots (<code>inventory.rbm_targets</code>) — days with noscheduler-written snapshot contribute 0.<b>ACH</b> is computed from source orders (<code>transaction.order</code>) for the range.</div></div><!-- >15 Days Aging Sold --><div class="sectionlabel">> 15 Days Aging Sold</div><div class="card"><div class="card-hd"><div><h2>Aging Inventory Cleared</h2><p class="desc">Billed value of items that were > 15 days old at sale, per RBM, for the picked range.</p></div><span class="badge-period" id="rdrrAgingPeriod">—</span></div><div id="rdrrAgingWrap"><div class="loading"><div class="spinner"></div>Loading…</div></div></div><!-- Partner Billing Count — unchanged, always current month MTD --><div class="sectionlabel">Partner Billing Count <spanstyle="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(Targeted partners only · bucketed by MTD ₹)</span></div><div class="card"><div class="card-hd"><div><h2>Partners by MTD Bracket</h2><p class="desc">Each targeted partner is counted in exactly one bracket by their current MTD billing.Zero-billed = partners who haven't crossed ₹1L this month.</p></div><span class="badge-period" id="rdrrBillingPeriod">—</span></div><div id="rdrrBillingWrap"><div class="loading"><div class="spinner"></div>Loading…</div></div></div><!-- RBM Performance Dashboard — JSON-driven, supports historical months via /rbm-drr/performance?month=YYYY-MM --><div class="sectionlabel">RBM Performance Dashboard</div><div class="card"><div class="card-hd"><div><h2>Composite MTD Ranking</h2><p class="desc">Rank of RBMs by weighted score across Talk Time (10%) + MTD Achievement (55%) + Aging (2%) +>1L Partners (29%) + Rating (2%) + Partners Rated (2%).<span style="color:var(--soft)"title="Aging denominator uses today's warehouse aging snapshot — for past months aging% is approximate.">Aging denominator is a live snapshot.</span></p></div><div style="display:flex;align-items:center;gap:10px"><select id="rdrrMonthPicker" class="ctl" style="min-width:170px;height:30px;padding:4px 10px"title="Pick a month to view that month's ranking"></select><span class="badge-period" id="rdrrPerfMonth">MTD</span></div></div><div id="rdrrPerfWrap"><div class="loading"><div class="spinner"></div>Loading…</div></div></div><!-- Today PO & Billed — Brand × RBM matrix (reuses /indent/today_po_table_fragment) --><div class="sectionlabel">Today PO & Billed — Brand × RBM</div><div class="card"><div class="card-hd"><div><h2>Purchase Milestones by Brand</h2><p class="desc">Today's PO value / partner count / total billed, per RBM × brand. Always today(independent of the range picker above).</p></div><select id="rdrrPmCategory" class="ctl" style="min-width:170px;height:34px;padding:6px 12px"><option value="RBM">All RBMs</option><option value="NSM">All NSMs</option><option value="RSM">All RSMs</option><option value="BM">All BMs</option><option value="DM">All DMs</option><option value="CM">All CMs</option><option value="ASM">All ASMs</option></select></div><div id="rdrrPmWrap"><div class="loading"><div class="spinner"></div>Loading…</div></div></div><!-- RBM Call Target Summary — full interactive page injected via iframe srcdoc (no backend change) --><div class="sectionlabel">RBM Call Target Summary</div><div class="card"><div class="card-hd"><div><h2>Today's Call Coverage per RBM</h2><p class="desc">Full interactive view — all popups, drill-downs and downloads work exactly like the standalonepage.</p></div><a class="badge-period" href="/indent/rbm_call_target" target="_blank" style="text-decoration:none">Open in newtab ›</a></div><iframe id="rdrrCallFrame" title="RBM Call Target Summary"style="width:100%;min-height:800px;border:0;border-radius:10px;background:#fff;display:block"></iframe></div></div><!-- achieved-vs-pending modal (opens from the RBMs ≥ 100% Target KPI card) --><div class="status-back" id="rdrrStatusBack"><div class="status-modal"><button class="x" id="rdrrStatusClose" aria-label="Close">×</button><h3>Target Status by RBM</h3><p class="desc" id="rdrrStatusDesc">—</p><div class="status-grid"><div class="status-col good"><h4>✅ Achieved <span class="cnt" id="rdrrStatusAchCnt">0</span></h4><div class="status-list" id="rdrrStatusAchList"></div></div><div class="status-col bad"><h4>⚠ Pending <span class="cnt" id="rdrrStatusPndCnt">0</span></h4><div class="status-list" id="rdrrStatusPndList"></div></div></div></div></div><!-- call-target drill-down drawer (Called Partners / OOS Calls) --><div class="rdrr-back" id="rdrrCallDrawerBack"><div class="rdrr-panel"><button class="x" id="rdrrCallDrawerClose" aria-label="Close">×</button><h3 id="rdrrCallDrawerTitle">—</h3><p class="desc" id="rdrrCallDrawerDesc">—</p><div id="rdrrCallDrawerBody"><div class="loading"><div class="spinner"></div>Loading…</div></div></div></div><!-- drill-down panel --><div class="rdrr-back" id="rdrrBack"><div class="rdrr-panel"><button class="x" id="rdrrClose" aria-label="Close">×</button><h3 id="rdrrDdTitle">—</h3><p class="desc" id="rdrrDdDesc">—</p><div class="rdrr-total"><div><div class="k">Achieved</div><div class="v" id="rdrrDdAch">—</div></div><div><div class="k">Target</div><div class="v" id="rdrrDdTgt">—</div></div><div><div class="k">Achievement</div><div class="v" id="rdrrDdPct">—</div></div></div><div class="sectionlabel" style="margin-top:2px">Movement Split — Achievement vs Target</div><div class="bbars" id="rdrrDdBars"></div><div class="sectionlabel">Warehouse Flow — Buying vs Selling <spanstyle="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(GRN into this warehouse + cross-warehouse direct billings)</span></div><div id="rdrrDdFlow"><div class="loading"><div class="spinner"></div>Loading…</div></div><div class="sectionlabel">Brand Mix — Share of Achievement <spanstyle="color:var(--soft);font-weight:600;text-transform:none;letter-spacing:0;margin-left:6px">(click a brand for movement split)</span></div><div id="rdrrDdBrands"><div class="loading"><div class="spinner"></div>Loading brand mix…</div></div></div></div><script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script><script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script><script>(function () {var DEFAULT_START = "$defaultStartDate";var DEFAULT_END = "$defaultEndDate";// Prefer query-string range if the caller provided one (e.g. from today_po_rbm's date picker).var url = new URL(window.location.href);var qsStart = url.searchParams.get("startDate");var qsEnd = url.searchParams.get("endDate");var startDate = qsStart || DEFAULT_START;var endDate = qsEnd || DEFAULT_END;function fmtDate(s) {return moment(s).format("DD MMM");}function fmtLakh(v) {v = Number(v) || 0;return (v / 100000).toFixed(1) + "L";}function pctClass(p) {if (p >= 95) return "good";if (p >= 41) return "warn";return "bad";}function pctLabel(ach, tgt) {if (!tgt || tgt <= 0) return {text: "-", cls: "na", num: null};var p = (ach / tgt) * 100;return {text: p.toFixed(1) + "%", cls: pctClass(p), num: p};}function pctBarWidth(ach, tgt) {if (!tgt || tgt <= 0) return 0;return Math.min(100, (ach / tgt) * 100);}// Initialise the range picker.$("#rdrrPeriod").daterangepicker({startDate: moment(startDate),endDate: moment(endDate),locale: {format: "YYYY-MM-DD", cancelLabel: "Clear"}});$("#rdrrPeriod").on("apply.daterangepicker", function (ev, picker) {startDate = picker.startDate.format("YYYY-MM-DD");endDate = picker.endDate.format("YYYY-MM-DD");});// Defensive: read the picker's current state at click time in case// the apply.daterangepicker event was swallowed for any reason.$("#rdrrApply").on("click", function () {var p = $("#rdrrPeriod").data("daterangepicker");if (p) {startDate = p.startDate.format("YYYY-MM-DD");endDate = p.endDate.format("YYYY-MM-DD");}load();// If a Sales role is currently active, re-fetch sales-data for the new range.if (CURRENT_SALES_ROLE) {$.ajax({url: "/rbm-drr/sales-data",type: "GET",data: {startDate: startDate, endDate: endDate},dataType: "json",success: function (payload) {SALES_PAYLOAD = payload;renderFiltered();},error: function () {SALES_PAYLOAD = {rows: []};renderFiltered();}});}});function load() {$("#rdrrTableWrap").html('<div class="loading"><div class="spinner"></div>Loading…</div>');$("#rdrrAgingWrap").html('<div class="loading"><div class="spinner"></div>Loading…</div>');$("#rdrrBillingWrap").html('<div class="loading"><div class="spinner"></div>Loading…</div>');$("#rdrrPeriodLabel").text(fmtDate(startDate) + " – " + fmtDate(endDate));$("#rdrrAgingPeriod").text(fmtDate(startDate) + " – " + fmtDate(endDate));fetch("/rbm-drr/data?startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"}).then(function (r) {return r.json().then(function (b) {return {ok: r.ok, body: b};});}).then(function (res) {if (!res.ok) {$("#rdrrTableWrap").html('<div class="empty">' + escapeHtml(res.body && res.body.error ? res.body.error : "Failed to load.") + '</div>');return;}render(res.body);}).catch(function (e) {$("#rdrrTableWrap").html('<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>');});fetch("/rbm-drr/extras?startDate=" + startDate + "&endDate=" + endDate, {credentials: "same-origin"}).then(function (r) {return r.json().then(function (b) {return {ok: r.ok, body: b};});}).then(function (res) {if (!res.ok) {var msg = '<div class="empty">' + escapeHtml(res.body && res.body.error ? res.body.error : "Failed to load.") + '</div>';$("#rdrrAgingWrap").html(msg);$("#rdrrBillingWrap").html(msg);return;}renderAging(res.body);renderPartnerBilling(res.body);}).catch(function (e) {var msg = '<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>';$("#rdrrAgingWrap").html(msg);$("#rdrrBillingWrap").html(msg);});}function renderAging(data) {var rows = data.aging || [];var totals = data.agingTotals || {};var balance = data.balance || {};if (rows.length === 0) {$("#rdrrAgingWrap").html('<div class="empty"><div class="ei">📦</div>No aging inventory sold in this range.</div>');return;}var html = ['<table class="xtable"><thead><tr>','<th>RBM</th>','<th class="num g-hid">HID</th>','<th class="num g-fast">FAST</th>','<th class="num g-run">RUN</th>','<th class="num g-slow">SLOW</th>','<th class="num g-other">OTHER</th>','<th class="num g-total">TOTAL</th>','</tr></thead><tbody>'];rows.forEach(function (r) {html.push('<tr>' +'<td class="rbm-nm">' + escapeHtml(r.rbmName || '-') + '</td>' +cell(r.hid, 'g-hid') + cell(r.fast, 'g-fast') + cell(r.run, 'g-run') +cell(r.slow, 'g-slow') + cell(r.other, 'g-other') + cell(r.total, 'g-total') +'</tr>');});html.push('<tr class="tot">' +'<td>Total</td>' +cell(totals.hid, 'g-hid') + cell(totals.fast, 'g-fast') + cell(totals.run, 'g-run') +cell(totals.slow, 'g-slow') + cell(totals.other, 'g-other') + cell(totals.total, 'g-total') +'</tr>');html.push('<tr class="bal">' +'<td class="hdr">15-Day Balance Value</td>' +cell(balance.hid) + cell(balance.fast) + cell(balance.run) +cell(balance.slow) + cell(balance.other) + cell(balance.total) +'</tr>');html.push('</tbody></table>');$("#rdrrAgingWrap").html(html.join(""));}function cell(v, cls) {v = Number(v) || 0;var zero = v === 0 ? ' zero' : '';var extra = cls ? ' ' + cls : '';return '<td class="num' + extra + zero + '">' + formatIndian(v) + '</td>';}function formatIndian(n) {n = Math.round(Number(n) || 0);if (n === 0) return '0';var s = n.toString();var lastThree = s.substring(s.length - 3);var otherNums = s.substring(0, s.length - 3);if (otherNums !== '') lastThree = ',' + lastThree;return otherNums.replace(/\B(?=(\d{2})+(?!\d))/g, ',') + lastThree;}function renderPartnerBilling(data) {var rows = data.partnerBilling || [];var totals = data.partnerBillingTotals || {};$("#rdrrBillingPeriod").text(data.partnerBillingMonth || '');if (rows.length === 0) {$("#rdrrBillingWrap").html('<div class="empty"><div class="ei">👥</div>No targeted partners this month.</div>');return;}var html = ['<table class="xtable"><thead><tr>','<th>RBM</th>','<th class="num">Targeted</th>','<th class="num">₹1L – 2L</th>','<th class="num">₹2L – 3L</th>','<th class="num">₹3L – 4L</th>','<th class="num">₹4L – 5L</th>','<th class="num g-total">₹5L & Above</th>','<th class="num">Zero Billed<br><small style="color:var(--soft);font-weight:600">(<1L)</small></th>','</tr></thead><tbody>'];rows.forEach(function (r) {html.push('<tr>' +'<td class="rbm-nm">' + escapeHtml(r.rbmName || '-') + '</td>' +'<td class="num">' + pill(r.targeted, '') + '</td>' +'<td class="num">' + pill(r.b1_2, r.b1_2 > 0 ? '' : 'zero') + '</td>' +'<td class="num">' + pill(r.b2_3, r.b2_3 > 0 ? '' : 'zero') + '</td>' +'<td class="num">' + pill(r.b3_4, r.b3_4 > 0 ? '' : 'zero') + '</td>' +'<td class="num">' + pill(r.b4_5, r.b4_5 > 0 ? '' : 'zero') + '</td>' +'<td class="num g-total">' + pill(r.b5, r.b5 > 0 ? 'good' : 'zero') + '</td>' +'<td class="num">' + pill(r.zero, r.zero > 0 ? 'warn' : 'zero') + '</td>' +'</tr>');});html.push('<tr class="tot">' +'<td>Total</td>' +'<td class="num">' + formatIndian(totals.targeted) + '</td>' +'<td class="num">' + formatIndian(totals.b1_2) + '</td>' +'<td class="num">' + formatIndian(totals.b2_3) + '</td>' +'<td class="num">' + formatIndian(totals.b3_4) + '</td>' +'<td class="num">' + formatIndian(totals.b4_5) + '</td>' +'<td class="num g-total">' + formatIndian(totals.b5) + '</td>' +'<td class="num">' + formatIndian(totals.zero) + '</td>' +'</tr>');html.push('</tbody></table>');$("#rdrrBillingWrap").html(html.join(""));}function pill(v, cls) {v = Number(v) || 0;var c = cls || (v === 0 ? 'zero' : '');return '<span class="cnt-pill ' + c + '">' + formatIndian(v) + '</span>';}function ratio(part, total) {part = Number(part) || 0;total = Number(total) || 0;if (total === 0) return 'zero';var p = (part / total) * 100;if (p >= 60) return 'good';if (p >= 30) return '';if (p > 0) return 'warn';return 'zero';}// Cached latest payload so the "Target Status by RBM" modal can render// without a re-fetch when the KPI card is clicked.var LAST_PAYLOAD = null;var SALES_PAYLOAD = null; // fetched from /rbm-drr/sales-data when a Sales role is selectedvar CURRENT_SALES_ROLE = null; // "ASM" / "CM" / … — when set with SALES_PAYLOAD, we render Sales viewvar CURRENT_SALES_AUTH_ID = null; // when set, further narrows Sales view to this person// Set of RBM authIds allowed by the current filter. null = no filter (show all).var CURRENT_FILTER_AUTH_IDS = null;function render(data) {LAST_PAYLOAD = data;renderFiltered();}// Maps a sales-data row into the same shape the RBM grid renderer expects.function normaliseSalesRow(r) {return {authId: r.salesAuthId, warehouseId: r.warehouseId,rbmName: r.salesName + (r.role ? " (" + r.role + ")" : ""),warehouseName: r.warehouseName,totalAch: r.totalAch, totalTgt: r.totalTgt,hidAch: r.hidAch, hidTgt: r.hidTgt,fastAch: r.fastAch, fastTgt: r.fastTgt,runAch: r.runAch, runTgt: r.runTgt,slowAch: r.slowAch, slowTgt: r.slowTgt,otherAch: r.otherAch, otherTgt: r.otherTgt};}// Renders KPIs + grid from the appropriate payload:// - Sales role active + payload loaded → filter SALES_PAYLOAD to that role (and optionally one person)// - RBM filter or no filter → LAST_PAYLOAD filtered by CURRENT_FILTER_AUTH_IDSfunction renderFiltered() {if (CURRENT_SALES_ROLE && SALES_PAYLOAD) {var salesRows = (SALES_PAYLOAD.rows || []).filter(function (r) {if (r.role !== CURRENT_SALES_ROLE) return false;if (CURRENT_SALES_AUTH_ID && String(r.salesAuthId) !== String(CURRENT_SALES_AUTH_ID)) return false;return true;}).map(normaliseSalesRow);renderRowsAsGrid(salesRows, true);return;}if (!LAST_PAYLOAD) return;var allRows = LAST_PAYLOAD.rows || [];var rows = CURRENT_FILTER_AUTH_IDS? allRows.filter(function (r) {return CURRENT_FILTER_AUTH_IDS[String(r.authId)];}): allRows;renderRowsAsGrid(rows, false);}// Extracted from renderFiltered() so both RBM-source and Sales-source paths share it.function renderRowsAsGrid(rows, isSalesView) {// Recompute totals from the filtered subset. Cheaper + correct vs using server totals.var t = {totalAch: 0, totalTgt: 0, totalBilled: 0, hidAch: 0, hidTgt: 0, fastAch: 0, fastTgt: 0,runAch: 0, runTgt: 0, slowAch: 0, slowTgt: 0, otherAch: 0, otherTgt: 0,rbmsAtOrAbove100: 0, rbmRowCount: rows.length};rows.forEach(function (r) {t.totalAch += Number(r.totalAch) || 0;t.totalTgt += Number(r.totalTgt) || 0;t.totalBilled += Number(r.actualBilled) || 0;t.hidAch += Number(r.hidAch) || 0;t.hidTgt += Number(r.hidTgt) || 0;t.fastAch += Number(r.fastAch) || 0;t.fastTgt += Number(r.fastTgt) || 0;t.runAch += Number(r.runAch) || 0;t.runTgt += Number(r.runTgt) || 0;t.slowAch += Number(r.slowAch) || 0;t.slowTgt += Number(r.slowTgt) || 0;t.otherAch += Number(r.otherAch) || 0;t.otherTgt += Number(r.otherTgt) || 0;if (r.totalTgt > 0 && r.totalAch >= r.totalTgt) t.rbmsAtOrAbove100++;});$("#rdrrKpiAch").html('₹ ' + fmtLakh(t.totalAch) +' <span class="rdrr-kpi-billed">/ ₹ ' + fmtLakh(t.totalBilled) + '</span>');$("#rdrrKpiTgt").html('₹ ' + fmtLakh(t.totalTgt));var pk = pctLabel(t.totalAch, t.totalTgt);$("#rdrrKpiPct").html(pk.cls === "na" ? '<span class="u">n/a</span>' :'<span class="pct ' + pk.cls + '">' + pk.text + '</span>');$("#rdrrKpiTop").html((t.rbmsAtOrAbove100 || 0) + ' <span class="u">/ ' + (t.rbmRowCount || 0) + '</span>');if (rows.length === 0) {var emptyMsg;if (isSalesView) {emptyMsg = 'No Sales target/achievement rows for this person in the range. Has the pushSalesTargets cron run today?';} else if (CURRENT_FILTER_AUTH_IDS) {emptyMsg = 'No RBM rows in this range for the selected filter.';} else {emptyMsg = 'No RBM rows in this range.';}$("#rdrrTableWrap").html('<div class="empty"><div class="ei">📭</div>' + emptyMsg + '</div>');return;}var buckets = [{key: "hid", label: "HID"},{key: "fast", label: "FAST"},{key: "run", label: "RUN"},{key: "slow", label: "SLOW"},{key: "other", label: "OTHER"}];var html = ['<table id="rdrrGrid"><thead><tr>','<th>RBM · Warehouse</th>','<th class="num g-total">Total<br><small style="color:var(--soft);font-weight:600">ACH / TGT</small></th>','<th class="num g-total">%</th>'];buckets.forEach(function (b) {html.push('<th class="num g-' + b.key + '">' + b.label + '<br><small style="color:var(--soft);font-weight:600">ACH / TGT</small></th>');html.push('<th class="num g-' + b.key + '">%</th>');});html.push('<th></th></tr></thead><tbody>');// Two-level collapse:// Level 1 — one "All RBMs (N)" row at the top, shows the grand-total.// Click → reveal per-RBM rows below.// Level 2 — per-RBM rows. Single-warehouse RBMs are directly clickable// (→ drill-down). Multi-warehouse RBMs collapse into a// group-parent showing summed totals; click → reveal// per-warehouse child rows.// Level 3 — per-warehouse rows (only under multi-warehouse RBMs).// Click → drill-down for that RBM × warehouse.// Default state on render: only the "All RBMs" row visible.var topRow = ['<tr class="top-parent">'];topRow.push('<td><div class="rbm-cell">' +'<span class="rn">' +'<span class="top-toggle">›</span> All RBMs' +'</span>' +'<span class="wh">' + rows.length + ' rows (click to expand)</span>' +'</div></td>');topRow.push(cellPair(t.totalAch, t.totalTgt));buckets.forEach(function (b) {topRow.push(cellPair(t[b.key + "Ach"], t[b.key + "Tgt"]));});topRow.push('<td class="chev"> </td>');topRow.push('</tr>');html.push(topRow.join(""));// Group rows per person (authId).var groups = new Map();var groupOrder = [];rows.forEach(function (r) {var key = String(r.authId);if (!groups.has(key)) {groups.set(key, {authId: r.authId, name: r.rbmName, children: []});groupOrder.push(key);}groups.get(key).children.push(r);});var groupCounter = 0;groupOrder.forEach(function (key) {var g = groups.get(key);if (g.children.length === 1) {// Single-warehouse person → RBM-tier row, directly clickable for drill-down.var r = g.children[0];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) + '">'];tr.push('<td><div class="rbm-cell"><span class="rn">' + escapeHtml(r.rbmName) + '</span><span class="wh">' + escapeHtml(r.warehouseName) + '</span></div></td>');tr.push(cellPair(r.totalAch, r.totalTgt));buckets.forEach(function (b) {tr.push(cellPair(r[b.key + "Ach"], r[b.key + "Tgt"]));});tr.push('<td class="chev">›</td>');tr.push('</tr>');html.push(tr.join(""));return;}// Multi-warehouse person → RBM-tier collapsed parent + hidden per-warehouse children.var gid = "g" + (groupCounter++);var sum = {totalAch: 0, totalTgt: 0};buckets.forEach(function (b) {sum[b.key + "Ach"] = 0;sum[b.key + "Tgt"] = 0;});g.children.forEach(function (r) {sum.totalAch += Number(r.totalAch) || 0;sum.totalTgt += Number(r.totalTgt) || 0;buckets.forEach(function (b) {sum[b.key + "Ach"] += Number(r[b.key + "Ach"]) || 0;sum[b.key + "Tgt"] += Number(r[b.key + "Tgt"]) || 0;});});var parent = ['<tr class="grp-parent tier-rbm" style="display:none" data-gid="' + gid + '">'];parent.push('<td><div class="rbm-cell">' +'<span class="rn">' + escapeHtml(g.name) + '</span>' +'<span class="wh"><span class="grp-toggle">›</span> ' +g.children.length + ' warehouses (click to expand)</span>' +'</div></td>');parent.push(cellPair(sum.totalAch, sum.totalTgt));buckets.forEach(function (b) {parent.push(cellPair(sum[b.key + "Ach"], sum[b.key + "Tgt"]));});parent.push('<td class="chev"> </td>');parent.push('</tr>');html.push(parent.join(""));g.children.forEach(function (r) {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) + '">'];tr.push('<td><div class="rbm-cell" style="padding-left:22px">' +'<span class="rn" style="font-weight:500;color:var(--soft)">↳ ' + escapeHtml(r.warehouseName) + '</span>' +'</div></td>');tr.push(cellPair(r.totalAch, r.totalTgt));buckets.forEach(function (b) {tr.push(cellPair(r[b.key + "Ach"], r[b.key + "Tgt"]));});tr.push('<td class="chev">›</td>');tr.push('</tr>');html.push(tr.join(""));});});// totals rowvar totTr = ['<tr class="totalrow"><td><strong>Total</strong></td>'];totTr.push(cellPair(t.totalAch, t.totalTgt, true));buckets.forEach(function (b) {totTr.push(cellPair(t[b.key + "Ach"], t[b.key + "Tgt"], true));});totTr.push('<td></td></tr>');html.push(totTr.join(""));html.push('</tbody></table>');$("#rdrrTableWrap").html(html.join(""));// Cache the parsed row on each <tr> so drill-down can render bars immediately.rows.forEach(function (r) {$("#rdrrGrid tbody tr").filter(function () {return +this.dataset.authid === r.authId && +this.dataset.warehouseid === r.warehouseId;}).each(function () {$(this).data("cached", r);});});$("#rdrrGrid tbody tr.clickable").on("click", function () {var d = this.dataset;var cached = $(this).data("cached");openDrilldown(+d.authid, +d.warehouseid, d.rbmname, d.warehousename, cached);});// Toggle collapsed groups: click parent row → show/hide the per-warehouse child rows.// Variable names deliberately avoid leading '$' — Velocity would try to parse them.$("#rdrrGrid tbody tr.grp-parent").on("click", function () {var gid = this.dataset.gid;var childRows = $("#rdrrGrid tbody tr.grp-" + gid);var isOpen = childRows.filter(":visible").length > 0;childRows.toggle(isOpen ? false : true);var toggleEl = $(this).find(".grp-toggle");toggleEl.css("transform", isOpen ? "rotate(0deg)" : "rotate(90deg)");toggleEl.css("display", "inline-block");});// Top-level "All RBMs" collapse: click → show/hide every RBM-tier row.// When collapsing, also force-hide any expanded warehouse child rows.$("#rdrrGrid tbody tr.top-parent").on("click", function () {var rbmRows = $("#rdrrGrid tbody tr.tier-rbm");var isOpen = rbmRows.filter(":visible").length > 0;rbmRows.toggle(isOpen ? false : true);if (isOpen) {// Collapsing → also hide any expanded warehouse children + reset their chevrons.$("#rdrrGrid tbody tr.grp-child").hide();$("#rdrrGrid tbody tr.grp-parent .grp-toggle").css("transform", "rotate(0deg)");}var topToggle = $(this).find(".top-toggle");topToggle.css("transform", isOpen ? "rotate(0deg)" : "rotate(90deg)");topToggle.css("display", "inline-block");});}function cellPair(ach, tgt, isTotal) {var p = pctLabel(ach, tgt);var vhtml = '<td class="num"><div class="cell-val">' + fmtLakh(ach) + ' <small>/ ' + fmtLakh(tgt) + '</small></div></td>';var barCls = p.cls === "na" ? "" : p.cls;var barW = pctBarWidth(ach, tgt);var pctHtml = '<td class="num"><span class="pct ' + p.cls + '">' + p.text + '</span>' +(barCls ? '<div class="bar"><i class="' + barCls + '" style="width:' + barW.toFixed(1) + '%"></i></div>' : '') +'</td>';return vhtml + pctHtml;}// --------------------------- drill-down ---------------------------function openDrilldown(authId, warehouseId, rbmName, warehouseName, cachedRow) {var back = $("#rdrrBack");$("#rdrrDdTitle").text(rbmName + " — " + warehouseName);$("#rdrrDdDesc").text(fmtDate(startDate) + " – " + fmtDate(endDate));$("#rdrrDdBars").empty();back.addClass("show");if (cachedRow) {renderBars(cachedRow);} else {$("#rdrrDdAch").text("—");$("#rdrrDdTgt").text("—");$("#rdrrDdPct").text("—");}$("#rdrrDdBrands").html('<div class="loading"><div class="spinner"></div>Loading brand mix…</div>');$("#rdrrDdFlow").html('<div class="loading"><div class="spinner"></div>Loading…</div>');var salesQs = CURRENT_SALES_ROLE ? ("&hierarchy=SALES&role=" + encodeURIComponent(CURRENT_SALES_ROLE)) : "";fetch("/rbm-drr/rbm-detail?authId=" + authId + "&warehouseId=" + warehouseId +"&startDate=" + startDate + "&endDate=" + endDate + salesQs, {credentials: "same-origin"}).then(function (r) {return r.json();}).then(function (data) {renderBrands(data);renderWarehouseFlow(data, cachedRow);}).catch(function (e) {var msg = '<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>';$("#rdrrDdBrands").html(msg);$("#rdrrDdFlow").html(msg);});}function renderWarehouseFlow(data, row) {var buying = (data && data.buying) || [];// Map buying values by status for O(1) lookup.var buyMap = {};buying.forEach(function (b) {buyMap[b.status] = Number(b.value) || 0;});// TEMP-DISABLED: opening-balance addition. Re-enable alongside the// controller block. Uncomment these two lines and swap the "Coverage"// computation below to (sell / (open + buy)).// var opening = (data && data.opening) || [];// var openMap = {}; opening.forEach(function (o) { openMap[o.status] = Number(o.value) || 0; });var labels = {HID: "HID", FASTMOVING: "FAST", RUNNING: "RUN", SLOWMOVING: "SLOW", OTHER: "OTHER"};var order = ["HID", "FASTMOVING", "RUNNING", "SLOWMOVING", "OTHER"];// Selling per bucket from the cached grid row (already has ACH per bucket).var sellMap = {HID: row ? Number(row.hidAch) || 0 : 0,FASTMOVING: row ? Number(row.fastAch) || 0 : 0,RUNNING: row ? Number(row.runAch) || 0 : 0,SLOWMOVING: row ? Number(row.slowAch) || 0 : 0,OTHER: row ? Number(row.otherAch) || 0 : 0};var totBuy = 0, totSell = 0;order.forEach(function (s) {totBuy += buyMap[s] || 0;totSell += sellMap[s] || 0;});// "Coverage" = selling / buying. High coverage = we sold most of what came in.// Low coverage = supply piling up (aging risk).function covCell(sell, buy) {if (!buy || buy <= 0) {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>';return '<span class="cov-cell na">—</span>';}var pct = (sell / buy) * 100;var cls = pct >= 60 ? 'good' : pct >= 25 ? 'warn' : 'bad';var w = Math.min(100, pct);return '<span class="cov-cell ' + cls + '">' +'<span class="cov-bar"><i style="width:' + w.toFixed(1) + '%"></i></span>' +'<span class="cov-pct">' + pct.toFixed(1) + '%</span></span>';}var html = '<table class="flow-tbl"><thead><tr>' +'<th>Movement</th><th class="num">Buying</th><th class="num">Selling</th><th class="num">Coverage (sold / bought)</th>' +'</tr></thead><tbody>';order.forEach(function (s) {var buy = buyMap[s] || 0, sell = sellMap[s] || 0;html += '<tr class="' + s.toLowerCase() + '">' +'<td><i></i>' + labels[s] + '</td>' +'<td class="num">' + fmtLakh(buy) + '</td>' +'<td class="num">' + fmtLakh(sell) + '</td>' +'<td class="num">' + covCell(sell, buy) + '</td>' +'</tr>';});html += '<tr class="tot">' +'<td>Total</td>' +'<td class="num">' + fmtLakh(totBuy) + '</td>' +'<td class="num">' + fmtLakh(totSell) + '</td>' +'<td class="num">' + covCell(totSell, totBuy) + '</td>' +'</tr>';html += '</tbody></table>';$("#rdrrDdFlow").html(html);}function renderBrands(data) {var brands = (data && data.brands) || [];if (brands.length === 0) {$("#rdrrDdBrands").html('<div class="empty"><div class="ei">🏷️</div>No brand sales in this range.</div>');return;}var maxShare = brands[0].sharePct || 0.001;var html = '<div class="brands">';brands.forEach(function (b, idx) {var barW = maxShare > 0 ? Math.min(100, (b.sharePct / maxShare) * 100) : 0;var topCls = idx === 0 ? ' top' : '';var pc = Number(b.partnerCount) || 0;var pcCls = pc === 0 ? ' zero' : '';var pcLabel = pc + (pc === 1 ? ' partner' : ' p');html += '<div class="brand-card' + topCls + '" data-idx="' + idx + '">' +'<div class="brand-head">' +'<div class="cx">›</div>' +'<div class="bn" title="' + escapeHtml(b.brand) + '">' + escapeHtml(b.brand) + '</div>' +'<div class="btrack"><i style="width:' + barW.toFixed(1) + '%"></i></div>' +'<div class="bpc' + pcCls + '" title="Distinct partners of this RBM who billed ' + escapeHtml(b.brand) + ' in the range">' +'<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>' +pcLabel +'</div>' +'<div class="bval">' + fmtLakh(b.valueSold) + '</div>' +'<div class="bshare">' + (b.sharePct || 0).toFixed(1) + '%</div>' +'</div>' +'<div class="brand-body">' +'<div class="bhdr">Movement Split within ' + escapeHtml(b.brand) + '</div>' +renderBrandBuckets(b.buckets || []) +'</div>' +'</div>';});html += '</div>';$("#rdrrDdBrands").html(html);$("#rdrrDdBrands .brand-head").on("click", function () {$(this).closest(".brand-card").toggleClass("open");});}function renderBrandBuckets(buckets) {if (!buckets || buckets.length === 0) {return '<div style="color:var(--soft);font-size:12px;padding:8px 4px">No movement split available.</div>';}var labels = {HID: "HID", FASTMOVING: "FAST", RUNNING: "RUN", SLOWMOVING: "SLOW", OTHER: "OTHER"};var html = '<div class="mbars">';buckets.forEach(function (bk) {var cls = (bk.status || "OTHER").toLowerCase();var label = labels[bk.status] || bk.status || "OTHER";var sp = bk.sharePctOfBrand || 0;var barW = Math.min(100, sp);html += '<div class="mbar ' + cls + '">' +'<div class="mname"><i></i>' + label + '</div>' +'<div class="mtrack"><i style="width:' + barW.toFixed(1) + '%"></i></div>' +'<div class="mval">' + fmtLakh(bk.valueSold) + '</div>' +'<div class="mshare">' + sp.toFixed(1) + '%</div>' +'</div>';});html += '</div>';return html;}function renderBars(row) {var totalAch = row.totalAch, totalTgt = row.totalTgt;$("#rdrrDdAch").html('₹ ' + fmtLakh(totalAch));$("#rdrrDdTgt").html('₹ ' + fmtLakh(totalTgt));var pt = pctLabel(totalAch, totalTgt);$("#rdrrDdPct").html(pt.cls === "na" ? '<span style="color:var(--soft);font-size:14px">n/a</span>' :'<span class="pct ' + pt.cls + '">' + pt.text + '</span>');var buckets = [{key: "hid", label: "HID"},{key: "fast", label: "FAST"},{key: "run", label: "RUN"},{key: "slow", label: "SLOW"},{key: "other", label: "OTHER"}];var html = "";buckets.forEach(function (b) {var ach = row[b.key + "Ach"], tgt = row[b.key + "Tgt"];var p = pctLabel(ach, tgt);var w = pctBarWidth(ach, tgt);html += '<div class="bbar ' + b.key + '">' +'<div class="bname"><i></i>' + b.label + '</div>' +'<div class="btrack"><i style="width:' + w.toFixed(1) + '%"></i></div>' +'<div class="btgt"><b>' + fmtLakh(ach) + '</b> / ' + fmtLakh(tgt) + '</div>' +'<div class="bpct ' + p.cls + '">' + p.text + '</div>' +'</div>';});$("#rdrrDdBars").html(html);}function escapeHtml(s) {if (s == null) return "";return String(s).replace(/[&<>"']/g, function (c) {return {"&": "&", "<": "<", ">": ">", '"': """, "'": "'"}[c];});}$("#rdrrClose").on("click", function () {$("#rdrrBack").removeClass("show");});$("#rdrrBack").on("click", function (e) {if (e.target === this) $(this).removeClass("show");});// Target-status modal (achieved vs pending) — opens from the KPI card.$("#rdrrKpiTopCard").on("click", function () {openStatusModal();});$("#rdrrStatusClose").on("click", function () {$("#rdrrStatusBack").removeClass("show");});$("#rdrrStatusBack").on("click", function (e) {if (e.target === this) $(this).removeClass("show");});function openStatusModal() {if (!LAST_PAYLOAD) return;// Respect the RBM filter so the modal matches what's on screen.var rows = (LAST_PAYLOAD.rows || []).filter(function (r) {return !CURRENT_FILTER_AUTH_IDS || CURRENT_FILTER_AUTH_IDS[String(r.authId)];});var achieved = [], pending = [];rows.forEach(function (r) {if (!r.totalTgt || r.totalTgt <= 0) return;var pct = (r.totalAch / r.totalTgt) * 100;var enriched = {row: r, pct: pct};if (r.totalAch >= r.totalTgt) achieved.push(enriched);else pending.push(enriched);});achieved.sort(function (a, b) {return b.pct - a.pct;});pending.sort(function (a, b) {return a.pct - b.pct;});$("#rdrrStatusDesc").text(fmtDate(startDate) + " – " + fmtDate(endDate));$("#rdrrStatusAchCnt").text(achieved.length);$("#rdrrStatusPndCnt").text(pending.length);$("#rdrrStatusAchList").html(renderStatusList(achieved, "achieved"));$("#rdrrStatusPndList").html(renderStatusList(pending, "pending"));$("#rdrrStatusBack").addClass("show");// Bind click on each item to open the same drill-down slide-in.$("#rdrrStatusBack .status-item").on("click", function () {var d = this.dataset;var cachedRow = null;(LAST_PAYLOAD.rows || []).some(function (r) {if (r.authId == d.authid && r.warehouseId == d.warehouseid) {cachedRow = r;return true;}return false;});$("#rdrrStatusBack").removeClass("show");openDrilldown(+d.authid, +d.warehouseid, d.rbmname, d.warehousename, cachedRow);});}function renderStatusList(items, kind) {if (items.length === 0) {return '<div class="status-empty">' +(kind === "achieved" ? "No RBM has met target in this range." : "All RBMs met target — nice!") +'</div>';}var html = "";items.forEach(function (it) {var r = it.row;var p = pctClass(it.pct);html += '<div class="status-item" title="Click to view drill-down"' +' data-authid="' + r.authId + '" data-warehouseid="' + r.warehouseId + '"' +' data-rbmname="' + escapeHtml(r.rbmName) + '" data-warehousename="' + escapeHtml(r.warehouseName) + '"' +' style="cursor:pointer">' +'<div><span class="rn">' + escapeHtml(r.rbmName) + '</span>' +'<span class="wh">' + escapeHtml(r.warehouseName) + '</span></div>' +'<div class="vv"><b>' + fmtLakh(r.totalAch) + '</b> / ' + fmtLakh(r.totalTgt) + '</div>' +'<div class="pp ' + p + '">' + it.pct.toFixed(1) + '%</div>' +'</div>';});return html;}// ------------------------- Today PO & Billed matrix -------------------------// Reuses the existing /indent/today_po_table_fragment endpoint (unchanged).// Parses the returned HTML into a data model and re-renders a compact// heatmap grid — one row per person, one column per brand, cell showing// Billed (primary) + Partners/PO (secondary), with per-brand heat shading.function loadPurchaseMilestones() {var cat = $("#rdrrPmCategory").val() || "RBM";$("#rdrrPmWrap").html('<div class="loading"><div class="spinner"></div>Loading…</div>');$.ajax({url: "/indent/today_po_table_fragment",type: "GET",data: {categoryType: cat},success: function (html) {try {var model = parsePurchaseMilestonesFragment(html);$("#rdrrPmWrap").html(renderPurchaseMilestonesGrid(model));} catch (e) {$("#rdrrPmWrap").html('<div class="empty">Failed to render purchase-milestones data.</div>');}},error: function () {$("#rdrrPmWrap").html('<div class="empty">Failed to load purchase-milestones data.</div>');}});}function numberToIndianCurrency(n) {if (n === 0) return "0";var s = n.toString();var lastThree = s.substring(s.length - 3);var other = s.substring(0, s.length - 3);if (other !== "") lastThree = "," + lastThree;return other.replace(/\B(?=(\d{2})+(?!\d))/g, ",") + lastThree;}// Compact Lakh format for the Purchase Milestones grid: 623478 → "6.2L", 500 → "0.0L", 15737358 → "157.4L".// Always one decimal, always suffixed "L" — matches how other DRR sections display money.function pmLakh(n) {n = Number(n) || 0;return (n / 100000).toFixed(1) + "L";}// Compact currency: >= 1Cr => "1.19Cr", >= 1L => "18.68L", >= 1K => "28.4K", else raw.// `tight=true` drops the space before the unit for narrow cells.function pmCompactCurrency(n, tight) {n = Number(n) || 0;var sp = tight ? "" : " ";if (n >= 10000000) return "₹" + (n / 10000000).toFixed(2) + sp + "Cr";if (n >= 100000) return "₹" + (n / 100000).toFixed(2) + sp + "L";if (n >= 1000) return "₹" + (n / 1000).toFixed(1) + sp + "K";return "₹" + numberToIndianCurrency(Math.round(n));}// Parse the injected multi-table fragment into:// { brands: [{name, slug}], rows: [{name, cells: {brand: {po, count, billed}}, totals: {po, count, billed}}] }function parsePurchaseMilestonesFragment(html) {var doc = new DOMParser().parseFromString(html, "text/html");var tables = doc.querySelectorAll("table");var brandOrder = [];var brandSet = {};var rowsMap = {}; // name -> {name, order, cells}var rowOrder = [];tables.forEach(function (tbl) {var theadRows = tbl.querySelectorAll("thead tr");if (theadRows.length < 1) return;// First thead row: [AuthUser th] + brand ths (colspan=3)var brandThs = theadRows[0].querySelectorAll("th[colspan]");var brands = [];brandThs.forEach(function (th) {var label = (th.textContent || "").trim().toUpperCase();if (!label) return;brands.push(label);if (!brandSet[label]) {brandSet[label] = true;brandOrder.push({name: label, slug: label.toLowerCase().replace(/[^a-z0-9]+/g, "")});}});tbl.querySelectorAll("tbody tr").forEach(function (tr) {var tds = tr.querySelectorAll("td");if (tds.length < 1 + brands.length * 3) return;var name = (tds[0].textContent || "").trim();if (!name) return;var row = rowsMap[name];if (!row) {row = {name: name, cells: {}, isTotal: /^all\b/i.test(name)};rowsMap[name] = row;rowOrder.push(name);}for (var i = 0; i < brands.length; i++) {var b = brands[i];var po = parseFloat((tds[1 + i * 3].textContent || "0").replace(/[^\d.-]/g, "")) || 0;var count = parseInt((tds[1 + i * 3 + 1].textContent || "0").replace(/[^\d.-]/g, ""), 10) || 0;var billed = parseFloat((tds[1 + i * 3 + 2].textContent || "0").replace(/[^\d.-]/g, "")) || 0;row.cells[b] = {po: po, count: count, billed: billed};}});});// Pull the TOTAL brand out of brandOrder and keep it first (fixed column).var totalIdx = brandOrder.findIndex(function (b) {return b.name === "TOTAL";});var totalBrand = totalIdx >= 0 ? brandOrder.splice(totalIdx, 1)[0] : null;// Sort remaining brands by grand total Billed (biggest first) using the "All …" row if present.var totalRowName = rowOrder.find(function (n) {return /^all\b/i.test(n);});var totalRow = totalRowName ? rowsMap[totalRowName] : null;if (totalRow) {brandOrder.sort(function (a, b) {var av = (totalRow.cells[a.name] || {}).billed || 0;var bv = (totalRow.cells[b.name] || {}).billed || 0;return bv - av;});}if (totalBrand) brandOrder.unshift(totalBrand);// Order rows: total first, others by grand total Billed (from TOTAL brand or sum).var totalRowIdx = totalRowName ? rowOrder.indexOf(totalRowName) : -1;if (totalRowIdx > 0) {rowOrder.splice(totalRowIdx, 1);rowOrder.unshift(totalRowName);}var head = totalRowIdx >= 0 ? [rowOrder.shift()] : [];rowOrder.sort(function (a, b) {var av = (rowsMap[a].cells["TOTAL"] || {}).billed || 0;var bv = (rowsMap[b].cells["TOTAL"] || {}).billed || 0;return bv - av;});rowOrder = head.concat(rowOrder);// Per-brand max Billed for heatmap intensity (excludes the totals row).var brandMax = {};brandOrder.forEach(function (b) {var max = 0;rowOrder.forEach(function (name) {var row = rowsMap[name];if (row.isTotal) return;var v = (row.cells[b.name] || {}).billed || 0;if (v > max) max = v;});brandMax[b.name] = max;});// The source fragment doesn't set a partner count on the "All RBMs" TOTAL// cell (grandTotal in MonitorController.getTodayPoTableFragment only sums// billed/PO/POBilled, not partiesBilled). Fill it in client-side by// summing each RBM's own TOTAL partner count — that column is the// distinct-across-brands count per RBM, so this sum is the most useful// aggregate available without a new backend query. Small risk of double-// counting if the same partner is served by two RBMs (rare, region-based).if (totalRow && totalRow.cells["TOTAL"]) {var sumTotalPartners = 0;rowOrder.forEach(function (name) {var r = rowsMap[name];if (r.isTotal) return;sumTotalPartners += ((r.cells["TOTAL"] || {}).count || 0);});if (sumTotalPartners > 0) {totalRow.cells["TOTAL"].count = sumTotalPartners;}}var rows = rowOrder.map(function (n) {return rowsMap[n];});return {brands: brandOrder, rows: rows, brandMax: brandMax};}function renderPurchaseMilestonesGrid(model) {if (!model.rows.length || !model.brands.length) {return '<div class="empty">No data.</div>';}var html = ['<div class="pm-grid-wrap"><table class="pm-grid"><thead><tr>'];html.push('<th class="pm-corner">' +'<div class="pm-corner-lbl">Person</div>' +'<div class="pm-corner-hint">' + model.rows.length + ' · ' + model.brands.length + ' brands</div>' +'</th>');model.brands.forEach(function (b) {html.push('<th class="pm-brand ' + b.slug + '">' + b.name + '</th>');});html.push('</tr></thead><tbody>');model.rows.forEach(function (row) {html.push('<tr class="pm-row' + (row.isTotal ? ' pm-total-row' : '') + '">');html.push('<td class="pm-name">' +'<div class="pm-name-txt">' + escapeHtml(row.name) + '</div>' +(row.isTotal ? '<div class="pm-name-sub">Grand total</div>' : '') +'</td>');model.brands.forEach(function (b) {var c = row.cells[b.name] || {po: 0, count: 0, billed: 0};var max = model.brandMax[b.name] || 0;var intensity = (!row.isTotal && max > 0) ? Math.min(1, c.billed / max) : 0;var isZero = c.billed === 0 && c.po === 0 && c.count === 0;// Softer blue heatmap: capped alpha keeps dark text readable at max intensity.var bgAlpha = intensity > 0 ? (0.05 + intensity * 0.22).toFixed(2) : 0;var style = (!row.isTotal && intensity > 0)? ' style="background: rgba(59,130,246,' + bgAlpha + ')"' : '';if (isZero) {html.push('<td class="pm-cell pm-cell-zero">—</td>');} else {// Values formatted in Lakh (X.XL) — matches how the drill-down and other DRR sections display money.var billedTxt = pmLakh(c.billed);var poTxt = pmLakh(c.po);var partnerLine = c.count > 0? ('<div class="pm-partners">' + c.count + '<span class="pm-tag"> p' + (c.count === 1 ? '' : 's') + '</span></div>'): '';var poLine = (c.po > 0 || c.billed > 0)? ('<div class="pm-po">' + poTxt + '<span class="pm-tag"> PO</span></div>'): '';var billedLine = '<div class="pm-billed">' + billedTxt + '</div>';// Order: partner (top) → PO (middle) → billed (bottom, primary)html.push('<td class="pm-cell' + (row.isTotal ? ' pm-cell-total' : '') + '"' + style + '>' +partnerLine + poLine + billedLine +'</td>');}});html.push('</tr>');});html.push('</tbody></table></div>');return html.join("");}function escapeHtml(s) {return String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");}$("#rdrrPmCategory").on("change", loadPurchaseMilestones);// ------------------------- Month picker + RBM Performance Dashboard -------------------------// The Composite MTD Ranking card now reads /rbm-drr/performance?month=YYYY-MM so historical// months can be viewed. Partner Billing Count re-fetches /rbm-drr/extras?month=YYYY-MM.// Aging/Billing extras aging bits are unaffected by the month picker — they stay driven by// the top-of-page date range.function fmtSeconds(sec) {sec = Math.max(0, Math.round(Number(sec) || 0));var h = Math.floor(sec / 3600);var m = Math.floor((sec % 3600) / 60);if (h > 0) return h + "h " + m + "m";return m + "m";}function pctCell(pct) {var p = Number(pct) || 0;return p.toFixed(1) + "%";}function renderPerformance(data) {var rows = data.rows || [];$("#rdrrPerfMonth").text(data.monthLabel || 'MTD');if (rows.length === 0) {$("#rdrrPerfWrap").html('<div class="empty"><div class="ei">📊</div>No performance dashboard data for this month.</div>');return;}var html = ['<div style="overflow-x:auto"><table class="xtable" id="rdrrPerfTable"><thead><tr>','<th style="width:48px">Rank</th>','<th>RBM</th>','<th class="num">Partners</th>','<th class="num" title="Talk time / expected (10%)">Talk Time<br><small>(10%)</small></th>','<th class="num" title="MTD achieved / monthly target (55%)">MTD Achievement<br><small>(55%)</small></th>','<th class="num" title="Aging sold / warehouse aging stock. Denominator is a live snapshot (2%).">15-Day Aging<br><small>(2%)</small></th>','<th class="num" title="Partners who crossed ₹1L this month (29%)">>1L Partners<br><small>(29%)</small></th>','<th class="num" title="Average partner rating (2%)">Avg Rating<br><small>(2%)</small></th>','<th class="num" title="Partners who rated / total partners (2%)">Partners Rated<br><small>(2%)</small></th>','<th class="num g-total">Score</th>','</tr></thead><tbody>'];rows.forEach(function (r) {var rankCell;if (r.rank === 1) rankCell = '<img src="/resources/images/king.png" style="width:28px" title="Rank 1">';else if (r.rank === 2) rankCell = '<img src="/resources/images/trophy-cup.png" style="width:24px" title="Rank 2">';else if (r.rank === 3) rankCell = '<img src="/resources/images/bronze-medal.png" style="width:24px" title="Rank 3">';else rankCell = String(r.rank);html.push('<tr>' +'<td class="num">' + rankCell + '</td>' +'<td class="rbm-nm">' + escapeHtml(r.rbmName || '-') + '</td>' +'<td class="num">' + (Number(r.partnerCount) || 0) + '</td>' +'<td class="num">' + fmtSeconds(r.talkSeconds) + '<br><small style="color:var(--soft)">' + pctCell(r.talkPct) + '</small></td>' +'<td class="num">' + pctCell(r.drrAchvPct) + '</td>' +'<td class="num">' + formatIndian(r.agingUnitsSold) + '<br><small style="color:var(--soft)">' + pctCell(r.agingPct) + '</small></td>' +'<td class="num">' + (Number(r.above1LPartnerCount) || 0) + '<br><small style="color:var(--soft)">' + pctCell(r.above1LPct) + '</small></td>' +'<td class="num">' + (Number(r.partnerRating) || 0).toFixed(2) + '</td>' +'<td class="num">' + (Number(r.ratingPartnerCount) || 0) + '</td>' +'<td class="num g-total">' + (Number(r.compositeScore) || 0).toFixed(1) + '</td>' +'</tr>');});html.push('</tbody></table></div>');$("#rdrrPerfWrap").html(html.join(""));}function loadPerformance(monthKey) {$("#rdrrPerfWrap").html('<div class="loading"><div class="spinner"></div>Loading…</div>');var qs = monthKey ? ("?month=" + encodeURIComponent(monthKey)) : "";fetch("/rbm-drr/performance" + qs, {credentials: "same-origin"}).then(function (r) {return r.json().then(function (b) {return {ok: r.ok, body: b};});}).then(function (res) {if (!res.ok) {$("#rdrrPerfWrap").html('<div class="empty">' + escapeHtml(res.body && res.body.error ? res.body.error : "Failed to load.") + '</div>');return;}renderPerformance(res.body);}).catch(function (e) {$("#rdrrPerfWrap").html('<div class="empty">Failed to load: ' + (e && e.message ? e.message : "network error") + '</div>');});}// Note: variable is named `sel` without the leading dollar on purpose.// Velocity would parse a leading-dollar name as a template reference and// choke on the embedded strings in the body.function populateMonthPicker() {var sel = $("#rdrrMonthPicker");if (sel.length === 0 || sel.data("populated")) return;var now = new Date();var opts = [];for (var i = 0; i < 6; i++) {var d = new Date(now.getFullYear(), now.getMonth() - i, 1);var y = d.getFullYear();var m = String(d.getMonth() + 1).padStart(2, "0");var key = y + "-" + m;var label = d.toLocaleString("en-US", {month: "long", year: "numeric"}) + (i === 0 ? " (current)" : "");opts.push('<option value="' + key + '">' + label + '</option>');}sel.html(opts.join(""));sel.data("populated", true);sel.on("change", function () {loadPerformance($(this).val());});}// ------------------------- RBM Call Target iframe -------------------------// The /indent/rbm_call_target endpoint returns a Velocity FRAGMENT (no <html>,// no Bootstrap/jQuery). When loaded directly in an iframe, Bootstrap modals// collapse and their inner Loading… contents render inline. Fix: fetch the// fragment, wrap it in a self-contained document that loads jQuery + Popper +// Bootstrap, then hand it to the iframe via srcdoc. Zero backend change.function loadCallTargetIframe() {$.ajax({url: "/indent/rbm_call_target",type: "GET",dataType: "html",success: function (fragment) {var doc = ['<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">','<meta name="viewport" content="width=device-width,initial-scale=1">','<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">','<style>body{margin:0;padding:16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:#fff}</style>','</head><body>',fragment,'<script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></' + 'script>','<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></' + 'script>','<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></' + 'script>','</body></html>'].join("");var frame = document.getElementById("rdrrCallFrame");frame.onload = function () {hookCallTargetDrawer(frame);};frame.srcdoc = doc;},error: function () {document.getElementById("rdrrCallFrame").srcdoc ='<div style="padding:24px;font-family:system-ui;color:#97a1b5">Failed to load call-target view.</div>';}});}// Redirect the iframe's Bootstrap-modal drill-downs into a slide-in parent drawer// that matches the dashboard style. srcdoc iframes inherit the parent origin so// we can freely reach into the iframe's window and override its functions.function hookCallTargetDrawer(frame) {var iw = frame.contentWindow;if (!iw) return;// Give the source's own <script> a moment to run and define the functions.setTimeout(function () {try {iw.showCalledPartnerDetails = function (authId, rbmName) {openCallTargetDrawer("called", authId, rbmName, iw.pageQueryDate);};iw.showOutOfSequenceDetails = function (authId, rbmName) {openCallTargetDrawer("oos", authId, rbmName, iw.pageQueryDate);};} catch (e) {// If cross-origin ever kicks in (shouldn't with srcdoc same-origin),// fall back to the iframe's native Bootstrap modal — no crash.}}, 50);}function openCallTargetDrawer(kind, authId, rbmName, dateStr) {var titleWho = rbmName + (dateStr ? " (" + dateStr + ")" : "");var title = (kind === "oos" ? "Out of Sequence Calls — " : "Called Partners — ") + titleWho;$("#rdrrCallDrawerTitle").text(title);$("#rdrrCallDrawerDesc").text(kind === "oos"? "Calls placed outside the recommended sequence.": "All calls made by this RBM today, with recordings.");$("#rdrrCallDrawerBody").html('<div class="loading"><div class="spinner"></div>Loading…</div>');$("#rdrrCallDrawerBack").addClass("show");var url;if (kind === "oos") {url = "/indent/rbm_call_target/oos_details?authId=" + encodeURIComponent(authId);} else {var dp = dateStr ? "&date=" + encodeURIComponent(dateStr) : "";url = "/indent/rbm_call_target/called_details?authId=" + encodeURIComponent(authId)+ "&agentName=" + encodeURIComponent(rbmName) + dp;}$.ajax({url: url, type: "GET", dataType: "json",success: function (data) {if (!data || data.length === 0) {$("#rdrrCallDrawerBody").html('<div class="empty"><div class="ei">📭</div>No entries.</div>');return;}$("#rdrrCallDrawerBody").html(kind === "oos" ? renderOosTable(data) : renderCalledTable(data));},error: function () {$("#rdrrCallDrawerBody").html('<div class="empty">Failed to load details.</div>');}});}function renderOosTable(data) {var html = '<table><thead><tr><th>S.No</th><th>Party Name</th><th>Code</th><th>Time</th></tr></thead><tbody>';data.forEach(function (r, i) {html += '<tr><td>' + (i + 1) + '</td><td>' + escapeHtml(r.partyName || "-") +'</td><td>' + escapeHtml(r.code || "-") + '</td><td>' + escapeHtml(r.time || "-") + '</td></tr>';});return html + '</tbody></table>';}function renderCalledTable(data) {var html = '<table><thead><tr>' +'<th>S.No</th><th>Party Name</th><th>Code</th><th>Remark</th>' +'<th>Call Status</th><th>Duration</th><th>Date/Time</th><th>Recording</th>' +'</tr></thead><tbody>';data.forEach(function (r, i) {var rowStyle = "";var recording = "-";if (r.breakLog) {var status = (r.breakStatus || "").toLowerCase();if (status.indexOf("available") >= 0) rowStyle = 'style="background:#fdecec;color:#721c24"';else if (status.indexOf("trying") >= 0) rowStyle = 'style="background:#fff5d6;color:#856404"';else if (status.indexOf("logged out") >= 0 || status.indexOf("logout") >= 0) rowStyle = 'style="background:#eef0f4;color:#383d41"';else if (status.indexOf("break") >= 0) rowStyle = 'style="background:#ffe5cc;color:#c45200"';}if (r.recordingUrl) {recording = '<audio controls preload="none" src="' + escapeHtml(r.recordingUrl) + '"></audio>';}html += '<tr ' + rowStyle + '>' +'<td>' + (i + 1) + '</td>' +'<td>' + escapeHtml(r.partyName || (r.breakLog ? (r.breakStatus || "-") : "-")) + '</td>' +'<td>' + escapeHtml(r.code || "-") + '</td>' +'<td>' + escapeHtml(r.remark || "-") + '</td>' +'<td>' + escapeHtml(r.callStatus || "-") + '</td>' +'<td>' + escapeHtml(r.callDuration || "-") + '</td>' +'<td>' + escapeHtml(r.callDateTime || "-") + '</td>' +'<td>' + recording + '</td>' +'</tr>';});return html + '</tbody></table>';}$("#rdrrCallDrawerClose").on("click", function () {$("#rdrrCallDrawerBack").removeClass("show");});$("#rdrrCallDrawerBack").on("click", function (e) {if (e.target === this) $(this).removeClass("show");});// ------------------------- cascading filter (Type → [Level] → Person) -------------------------// Flow:// Type = RBM → Person dropdown = all RBMs. Pick one to filter to that RBM.// Type = SALES → Level dropdown appears (ASM/CM/DM/BM/RSM/NSM). Pick a level// → Person dropdown = active people at that level. Pick a person// → fetches /rbm-drr/sales-data and shows that person's aggregated rows.// One backend call caches both the RBM list and the Sales list.var ALL_RBMS = []; // [{authId, name}, ...]var ALL_SALES_BY_ROLE = {}; // {ASM: [...], CM: [...], ...}function loadFilterOptions() {$.ajax({url: "/rbm-drr/all-sales", type: "GET", dataType: "json",success: function (payload) {ALL_RBMS = (payload && payload.rbms) || [];ALL_SALES_BY_ROLE = {};((payload && payload.sales) || []).forEach(function (s) {(ALL_SALES_BY_ROLE[s.role] = ALL_SALES_BY_ROLE[s.role] || []).push(s);});repopulatePersonForCurrentType();}});}// Refill the Person dropdown based on current Type (+ Level if Sales).function repopulatePersonForCurrentType() {var type = $("#rdrrTypeSelect").val();var personSel = $("#rdrrPersonSelect");if (type === "RBM") {var html = '<option value="">All RBMs (' + ALL_RBMS.length + ')</option>';ALL_RBMS.forEach(function (r) {html += '<option value="' + r.authId + '">' + escapeHtml(r.name || "Unknown") + '</option>';});personSel.html(html).prop("disabled", false);return;}// SALESvar level = $("#rdrrLevelSelect").val();if (!level) {personSel.html('<option value="">Pick a level first</option>').prop("disabled", true);return;}var list = ALL_SALES_BY_ROLE[level] || [];var salesHtml = '<option value="">All ' + escapeHtml(level) + 's (' + list.length + ')</option>';list.forEach(function (s) {salesHtml += '<option value="' + s.authId + '">' + escapeHtml(s.name || "Unknown") + '</option>';});personSel.html(salesHtml).prop("disabled", false);}// Fetches (or refetches) /rbm-drr/sales-data for the current date range,// then renders. Called on Level change and on Apply while Sales view active.function fetchAndRenderSales() {$("#rdrrTableWrap").html('<div class="loading"><div class="spinner"></div>Loading…</div>');$.ajax({url: "/rbm-drr/sales-data", type: "GET",data: {startDate: startDate, endDate: endDate}, dataType: "json",success: function (payload) {SALES_PAYLOAD = payload;renderFiltered();},error: function () {SALES_PAYLOAD = {rows: []};renderFiltered();}});}// Type toggles: show/hide Level; reset filter state; refresh Person options.$("#rdrrTypeSelect").on("change", function () {var type = $(this).val();if (type === "SALES") {$("#rdrrLevelSelect").css("display", "").val("");} else {$("#rdrrLevelSelect").css("display", "none").val("");}// Clear all filter state whenever the type changes.CURRENT_FILTER_AUTH_IDS = null;CURRENT_SALES_ROLE = null;CURRENT_SALES_AUTH_ID = null;SALES_PAYLOAD = null;repopulatePersonForCurrentType();renderFiltered();});$("#rdrrLevelSelect").on("change", function () {var level = $(this).val();// Reset the person-specific filter; role determines the view.CURRENT_FILTER_AUTH_IDS = null;CURRENT_SALES_AUTH_ID = null;CURRENT_SALES_ROLE = level || null;repopulatePersonForCurrentType();if (level) {// Fetch Sales-data once, then all subsequent role/person filtering is client-side.fetchAndRenderSales();} else {SALES_PAYLOAD = null;renderFiltered();}});$("#rdrrPersonSelect").on("change", function () {var authId = $(this).val();var type = $("#rdrrTypeSelect").val();if (type === "RBM") {// RBM filter (single authId) — no server round trip needed.CURRENT_SALES_ROLE = null;CURRENT_SALES_AUTH_ID = null;SALES_PAYLOAD = null;if (!authId) {CURRENT_FILTER_AUTH_IDS = null;} else {var single = {};single[String(authId)] = true;CURRENT_FILTER_AUTH_IDS = single;}renderFiltered();return;}// SALES type — role already active from Level dropdown; SALES_PAYLOAD already fetched.// Person selection just further filters client-side (no re-fetch).CURRENT_FILTER_AUTH_IDS = null;CURRENT_SALES_AUTH_ID = authId || null;if (SALES_PAYLOAD) {renderFiltered();} else if (CURRENT_SALES_ROLE) {// Edge case: person changed before Level's fetch completed — trigger the fetch.fetchAndRenderSales();}});// initial loadloadFilterOptions();populateMonthPicker();load();loadPurchaseMilestones();loadPerformance($("#rdrrMonthPicker").val() || null);loadCallTargetIframe();})();</script></body></html>