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