| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>XLM Product Performance Dashboard</title> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&display=swap'); |
| :root{--bg:#0d1117;--bg2:#161b22;--card:#1c2333;--card2:#212940;--bdr:#30363d;--t1:#e6edf3;--t2:#8b949e;--t3:#6e7681;--blue:#58a6ff;--green:#3fb950;--yellow:#d29922;--red:#f85149;--purple:#bc8cff;--cyan:#39c5cf;} |
| *{margin:0;padding:0;box-sizing:border-box;} |
| body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--t1);font-size:13px;min-height:100vh;} |
| .sidebar{position:fixed;left:0;top:0;bottom:0;width:196px;background:var(--bg2);border-right:1px solid var(--bdr);z-index:100;display:flex;flex-direction:column;overflow-y:auto;} |
| .logo{display:flex;align-items:center;gap:10px;padding:16px;border-bottom:1px solid var(--bdr);} |
| .logo-icon{width:36px;height:36px;background:linear-gradient(135deg,var(--blue),var(--purple));border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;color:#fff;flex-shrink:0;} |
| .logo-text{font-size:11px;color:var(--t2);line-height:1.3;} |
| .logo-text b{display:block;color:var(--t1);font-size:12px;} |
| .nav{padding:8px 0;flex:1;} |
| .nav-item{display:flex;align-items:center;gap:9px;padding:9px 16px;cursor:pointer;color:var(--t2);font-size:12px;font-weight:500;border-left:3px solid transparent;transition:all .15s;user-select:none;} |
| .nav-item:hover{background:var(--card);color:var(--t1);} |
| .nav-item.active{background:rgba(88,166,255,.1);color:var(--blue);border-left-color:var(--blue);} |
| .nav-icon{font-size:14px;width:18px;text-align:center;flex-shrink:0;} |
| .main{margin-left:196px;} |
| .header{background:var(--bg2);border-bottom:1px solid var(--bdr);padding:14px 22px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:50;} |
| .header-left h1{font-size:19px;font-weight:700;} |
| .header-left p{font-size:11px;color:var(--t2);margin-top:2px;} |
| .header-right{display:flex;align-items:center;gap:8px;} |
| .dropdown{position:relative;} |
| .btn{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:7px;font-size:12px;font-weight:500;cursor:pointer;border:1px solid var(--bdr);background:var(--card);color:var(--t1);transition:all .15s;white-space:nowrap;font-family:inherit;} |
| .btn:hover{background:var(--card2);border-color:var(--blue);} |
| .btn.primary{background:var(--blue);border-color:var(--blue);color:#fff;} |
| .btn.primary:hover{background:#4493e0;} |
| .dropdown-menu{display:none;position:absolute;top:calc(100% + 6px);right:0;background:var(--card);border:1px solid var(--bdr);border-radius:8px;min-width:190px;z-index:999;box-shadow:0 8px 24px rgba(0,0,0,.5);overflow:hidden;} |
| .dropdown-menu.open{display:block;} |
| .dropdown-item{padding:9px 14px;font-size:12px;cursor:pointer;color:var(--t1);transition:background .12s;} |
| .dropdown-item:hover{background:var(--card2);} |
| .dropdown-item.selected{color:var(--blue);font-weight:600;} |
| .content{padding:18px 22px;} |
| .section{display:none;} |
| .section.active{display:block;} |
| .kpi-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:11px;margin-bottom:14px;} |
| .kpi-card{background:var(--card);border:1px solid var(--bdr);border-radius:10px;padding:13px 14px;transition:all .15s;} |
| .kpi-card:hover{border-color:var(--blue);transform:translateY(-1px);} |
| .kpi-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:15px;margin-bottom:8px;} |
| .kpi-label{font-size:10.5px;color:var(--t2);margin-bottom:3px;} |
| .kpi-value{font-size:20px;font-weight:700;line-height:1;} |
| .kpi-change{font-size:10.5px;margin-top:5px;} |
| .up{color:var(--green);} |
| .down{color:var(--red);} |
| .neutral{color:var(--t3);} |
| .g3{display:grid;grid-template-columns:repeat(3,1fr);gap:13px;margin-bottom:13px;} |
| .g4{display:grid;grid-template-columns:2fr 1.1fr 1.1fr 1fr;gap:13px;margin-bottom:13px;} |
| .g2{display:grid;grid-template-columns:1fr 1fr;gap:13px;margin-bottom:13px;} |
| .card{background:var(--card);border:1px solid var(--bdr);border-radius:10px;padding:15px;} |
| .card-title{font-size:12px;font-weight:600;margin-bottom:13px;display:flex;align-items:center;justify-content:space-between;} |
| .card-title .sub{color:var(--t3);font-weight:400;font-size:10.5px;} |
| .donut-wrap{display:flex;align-items:center;gap:14px;} |
| .donut-rel{position:relative;flex-shrink:0;} |
| .donut-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;} |
| .donut-center .big{font-size:20px;font-weight:700;line-height:1;} |
| .donut-center .sm{font-size:10px;color:var(--t3);} |
| .legend{display:flex;flex-direction:column;gap:5px;} |
| .li{display:flex;align-items:center;gap:7px;font-size:11px;} |
| .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;} |
| .lv{margin-left:auto;font-weight:600;font-size:11px;} |
| .table-wrap{overflow-x:auto;} |
| table{width:100%;border-collapse:collapse;font-size:11.5px;} |
| th{background:var(--bg2);padding:8px 10px;text-align:left;color:var(--t2);font-weight:600;font-size:10.5px;white-space:nowrap;border-bottom:1px solid var(--bdr);cursor:pointer;user-select:none;} |
| th:hover{color:var(--t1);} |
| th.sorted{color:var(--blue);} |
| td{padding:9px 10px;border-bottom:1px solid rgba(48,54,61,.4);white-space:nowrap;} |
| tr:hover td{background:rgba(255,255,255,.02);} |
| .badge{display:inline-block;padding:2px 9px;border-radius:20px;font-size:10px;font-weight:600;} |
| .b-high{background:rgba(248,81,73,.12);color:var(--red);border:1px solid rgba(248,81,73,.3);} |
| .b-medium{background:rgba(210,153,34,.12);color:var(--yellow);border:1px solid rgba(210,153,34,.3);} |
| .b-low{background:rgba(63,185,80,.12);color:var(--green);border:1px solid rgba(63,185,80,.3);} |
| .hl{display:flex;flex-direction:column;gap:7px;} |
| .hl-item{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border-radius:8px;font-size:11px;line-height:1.45;} |
| .hl-good{background:rgba(63,185,80,.07);border:1px solid rgba(63,185,80,.2);} |
| .hl-bad{background:rgba(248,81,73,.07);border:1px solid rgba(248,81,73,.2);} |
| .hl-neu{background:rgba(88,166,255,.07);border:1px solid rgba(88,166,255,.2);} |
| .hl-ico{font-size:14px;flex-shrink:0;margin-top:1px;} |
| .note{font-size:10px;color:var(--t3);text-align:center;padding:10px;border-top:1px solid var(--bdr);margin-top:6px;} |
| .filter-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;align-items:center;} |
| .filter-label{font-size:11px;color:var(--t2);} |
| .filter-btn{padding:5px 11px;border-radius:20px;border:1px solid var(--bdr);background:var(--bg2);color:var(--t2);font-size:11px;cursor:pointer;transition:all .15s;} |
| .filter-btn:hover,.filter-btn.on{background:rgba(88,166,255,.12);border-color:var(--blue);color:var(--blue);} |
| .toast{position:fixed;bottom:24px;right:24px;background:var(--card);border:1px solid var(--bdr);border-radius:8px;padding:12px 18px;font-size:12px;z-index:400;transform:translateY(80px);opacity:0;transition:all .3s;} |
| .toast.show{transform:translateY(0);opacity:1;} |
| .ch{position:relative;} |
| select.fselect{background:var(--bg2);border:1px solid var(--bdr);color:var(--t1);font-size:11px;padding:5px 8px;border-radius:6px;cursor:pointer;font-family:inherit;} |
| </style> |
| </head> |
| <body> |
|
|
| <aside class="sidebar"> |
| <div class="logo"> |
| <div class="logo-icon" style="background: transparent;"> |
| <img src="cadence-logo-black.png" alt="Cadence Logo" style="width: 100%; height: auto; display: block;"> |
| </div> |
| </div> |
| <nav class="nav" id="mainNav"> |
| <div class="nav-item active" data-sec="overview"><span class="nav-icon">β¬</span>Overview</div> |
| <div class="nav-item" data-sec="perf-summary"><span class="nav-icon">π</span>Performance Summary</div> |
| <div class="nav-item" data-sec="customer-perf"><span class="nav-icon">π₯</span>Customer Performance</div> |
| <div class="nav-item" data-sec="product-quality"><span class="nav-icon">π¨</span>Product Quality</div> |
| <div class="nav-item" data-sec="engagement"><span class="nav-icon">π¬</span>Engagement & Sentiment</div> |
| <div class="nav-item" data-sec="pps-section"><span class="nav-icon">π</span>Product Perf Score</div> |
| <div class="nav-item" data-sec="risk-gaps"><span class="nav-icon">β οΈ</span>Risk & Gaps</div> |
| <div class="nav-item" data-sec="revenue-section"><span class="nav-icon">π°</span>Revenue</div> |
| <div class="nav-item" data-sec="highlights"><span class="nav-icon">π‘</span>Highlights & Insights</div> |
| <div class="nav-item" data-sec="data-explorer"><span class="nav-icon">π</span>Data Explorer</div> |
| <div class="nav-item" data-sec="definitions"><span class="nav-icon">π</span>Definitions</div> |
| </nav> |
| </aside> |
|
|
| <main class="main"> |
| <header class="header"> |
| <div class="header-left"> |
| <h1>XLM Product Performance Dashboard</h1> |
| <p>Executive Overview</p> |
| </div> |
| <div class="header-right"> |
| <div class="dropdown" id="ddQ"> |
| <button class="btn" id="btnQ">π
<span id="lblQ">Q4 FY2025 (OctβDec 2025)</span> βΎ</button> |
| <div class="dropdown-menu" id="menuQ"> |
| <div class="dropdown-item" data-q="Q1-2024">Q1 FY2024 (JanβMar 2024)</div> |
| <div class="dropdown-item" data-q="Q2-2024">Q2 FY2024 (AprβJun 2024)</div> |
| <div class="dropdown-item" data-q="Q3-2024">Q3 FY2024 (JulβSep 2024)</div> |
| <div class="dropdown-item" data-q="Q4-2024">Q4 FY2024 (OctβDec 2024)</div> |
| <div class="dropdown-item" data-q="Q1-2025">Q1 FY2025 (JanβMar 2025)</div> |
| <div class="dropdown-item" data-q="Q2-2025">Q2 FY2025 (AprβJun 2025)</div> |
| <div class="dropdown-item" data-q="Q3-2025">Q3 FY2025 (JulβSep 2025)</div> |
| <div class="dropdown-item selected" data-q="Q4-2025">Q4 FY2025 (OctβDec 2025)</div> |
| </div> |
| </div> |
| <div class="dropdown" id="ddC"> |
| <button class="btn" id="btnC">Compare to <span id="lblC">Q3 FY2025</span> βΎ</button> |
| <div class="dropdown-menu" id="menuC"> |
| <div class="dropdown-item" data-c="Q1-2024">Q1 FY2024</div> |
| <div class="dropdown-item" data-c="Q2-2024">Q2 FY2024</div> |
| <div class="dropdown-item" data-c="Q3-2024">Q3 FY2024</div> |
| <div class="dropdown-item" data-c="Q4-2024">Q4 FY2024</div> |
| <div class="dropdown-item" data-c="Q1-2025">Q1 FY2025</div> |
| <div class="dropdown-item" data-c="Q2-2025">Q2 FY2025</div> |
| <div class="dropdown-item selected" data-c="Q3-2025">Q3 FY2025</div> |
| </div> |
| </div> |
| <div class="dropdown" id="ddExp"> |
| <button class="btn primary" id="btnExp">β¬ Export βΎ</button> |
| <div class="dropdown-menu" id="menuExp"> |
| <div class="dropdown-item" id="doCSV">π Export as CSV</div> |
| <div class="dropdown-item" id="doJSON">π Export as JSON</div> |
| <div class="dropdown-item" id="doPrint">π¨ Print / Save as PDF</div> |
| </div> |
| </div> |
| </div> |
| </header> |
|
|
| <div class="content"> |
|
|
| |
| <div class="section active" id="overview"> |
| <div class="kpi-grid" id="kpiGrid"></div> |
| <div class="g3"> |
| <div class="card"> |
| <div class="card-title">Product Performance Score Trend <span class="sub">All quarters</span></div> |
| <div class="ch" style="height:175px"><canvas id="cPPS"></canvas></div> |
| </div> |
| <div class="card"> |
| <div class="card-title">Score Breakdown <span class="sub" id="sbTitle"></span></div> |
| <div class="donut-wrap" style="height:175px"> |
| <div class="donut-rel" style="width:148px;height:148px"> |
| <canvas id="cBreak" width="148" height="148"></canvas> |
| <div class="donut-center"><div class="big" id="dBig">β</div><div class="sm">/100</div></div> |
| </div> |
| <div class="legend"> |
| <div class="li"><div class="dot" style="background:var(--blue)"></div>Engagement<span class="lv" id="vEng">β</span></div> |
| <div class="li"><div class="dot" style="background:var(--green)"></div>Sentiment<span class="lv" id="vSent">β</span></div> |
| <div class="li"><div class="dot" style="background:var(--yellow)"></div>Satisfaction<span class="lv" id="vSat">β</span></div> |
| <div style="font-size:10px;color:var(--t3);margin-top:8px;line-height:1.6"><b style="color:var(--t2)">PPS Formula:</b><br>(EngagementΓ0.6)ΓSentiment<br>ΓSatisfaction β Normalized 0β100</div> |
| </div> |
| </div> |
| </div> |
| <div class="card"> |
| <div class="card-title">Performance Scores Over Time</div> |
| <div class="ch" style="height:175px"><canvas id="cPOT"></canvas></div> |
| </div> |
| </div> |
| <div class="g4"> |
| <div class="card"> |
| <div class="card-title">Revenue Trend <span class="sub">$ in Millions</span></div> |
| <div class="ch" style="height:165px"><canvas id="cRev"></canvas></div> |
| </div> |
| <div class="card"> |
| <div class="card-title">Business Risk <span class="sub" id="riskSubTitle"></span></div> |
| <div style="display:flex;flex-direction:column;align-items:center;gap:8px;height:165px;justify-content:center"> |
| <div class="donut-rel" style="width:105px;height:105px"> |
| <canvas id="cRisk" width="105" height="105"></canvas> |
| <div class="donut-center"><div class="big" style="font-size:16px" id="riskN">10</div><div class="sm">Customers</div></div> |
| </div> |
| <div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center"> |
| <div class="li"><div class="dot" style="background:var(--red)"></div>High<span class="lv" style="margin-left:4px" id="rH">β</span></div> |
| <div class="li"><div class="dot" style="background:var(--yellow)"></div>Med<span class="lv" style="margin-left:4px" id="rM">β</span></div> |
| <div class="li"><div class="dot" style="background:var(--green)"></div>Low<span class="lv" style="margin-left:4px" id="rL">β</span></div> |
| </div> |
| </div> |
| </div> |
| <div class="card"> |
| <div class="card-title">Gap vs OT <span class="sub" id="gapSubTitle"></span></div> |
| <div style="display:flex;flex-direction:column;align-items:center;gap:8px;height:165px;justify-content:center"> |
| <div class="donut-rel" style="width:105px;height:105px"> |
| <canvas id="cGap" width="105" height="105"></canvas> |
| <div class="donut-center"><div class="big" style="font-size:16px" id="gapN">10</div><div class="sm">Customers</div></div> |
| </div> |
| <div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center"> |
| <div class="li"><div class="dot" style="background:var(--red)"></div>>15%<span class="lv" style="margin-left:4px" id="gH">β</span></div> |
| <div class="li"><div class="dot" style="background:var(--yellow)"></div>5β15%<span class="lv" style="margin-left:4px" id="gM">β</span></div> |
| <div class="li"><div class="dot" style="background:var(--green)"></div><5%<span class="lv" style="margin-left:4px" id="gL">β</span></div> |
| </div> |
| </div> |
| </div> |
| <div class="card"> |
| <div class="card-title">Quarter Highlights</div> |
| <div class="hl" id="hlBox"></div> |
| </div> |
| </div> |
| <div class="card" style="margin-bottom:12px"> |
| <div class="card-title">Customer Performance Overview <span class="sub" id="tblTitle"></span></div> |
| <div class="table-wrap"> |
| <table id="mainTable"> |
| <thead><tr> |
| <th data-col="name">Customer <span>β</span></th> |
| <th data-col="quarter">Quarter <span>β</span></th> |
| <th data-col="revenue">Revenue <span>β</span></th> |
| <th data-col="pps">PPS (0β100) <span>β</span></th> |
| <th data-col="build">Build <span>β</span></th> |
| <th data-col="sim">Simulation <span>β</span></th> |
| <th data-col="eng">Engagement <span>β</span></th> |
| <th data-col="sent">Sentiment <span>β</span></th> |
| <th data-col="sat">Satisfaction <span>β</span></th> |
| <th data-col="gap">Gap vs OT <span>β</span></th> |
| <th data-col="risk">Risk <span>β</span></th> |
| </tr></thead> |
| <tbody id="mainTbody"></tbody> |
| </table> |
| </div> |
| </div> |
| <div class="note">Note: Scores averaged from weekly data. PPS = (EngagementΓ0.6)ΓSentimentΓSatisfaction (Normalized 0β100)</div> |
| </div> |
|
|
| |
| <div class="section" id="perf-summary"> |
| <div class="card" style="margin-bottom:13px"> |
| <div class="card-title">Quarter-over-Quarter Summary <span class="sub">βΆ = current quarter | β· = compare quarter</span></div> |
| <div class="table-wrap"><table><thead><tr> |
| <th>Quarter</th><th>Revenue ($M)</th><th>PPS</th><th>Engagement</th><th>Sentiment</th><th>Satisfaction</th><th>Build</th><th>Simulation</th><th>Gap vs OT</th> |
| </tr></thead><tbody id="perfBody"></tbody></table></div> |
| </div> |
| <div class="g2"> |
| <div class="card"><div class="card-title">Revenue by Quarter</div><div class="ch" style="height:220px"><canvas id="cRevPS"></canvas></div></div> |
| <div class="card"><div class="card-title">PPS vs Engagement Over Time</div><div class="ch" style="height:220px"><canvas id="cPPSPS"></canvas></div></div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="customer-perf"> |
| <div class="filter-row"> |
| <span class="filter-label">Filter by Risk:</span> |
| <div class="filter-btn on" data-risk="All">All</div> |
| <div class="filter-btn" data-risk="High">High</div> |
| <div class="filter-btn" data-risk="Medium">Medium</div> |
| <div class="filter-btn" data-risk="Low">Low</div> |
| </div> |
| <div class="card" style="margin-bottom:13px"> |
| <div class="card-title">Customer Detail <span class="sub" id="cpTitle"></span></div> |
| <div class="table-wrap"><table><thead><tr> |
| <th>Customer</th><th>Revenue</th><th>PPS</th><th>Build</th><th>Simulation</th><th>Engagement</th><th>Sentiment</th><th>Satisfaction</th><th>Gap vs OT</th><th>Risk</th> |
| </tr></thead><tbody id="cpBody"></tbody></table></div> |
| </div> |
| <div class="card"><div class="card-title">Revenue by Customer <span class="sub" id="cpRevTitle"></span></div><div class="ch" style="height:240px"><canvas id="cCustRev"></canvas></div></div> |
| </div> |
|
|
| |
| <div class="section" id="product-quality"> |
| <div class="g2"> |
| <div class="card"><div class="card-title">Build Performance Trend</div><div class="ch" style="height:220px"><canvas id="cBuild"></canvas></div></div> |
| <div class="card"><div class="card-title">Simulation Performance Trend</div><div class="ch" style="height:220px"><canvas id="cSim"></canvas></div></div> |
| </div> |
| <div class="card"><div class="card-title">Build vs Simulation by Customer <span class="sub" id="bsTitle"></span></div><div class="ch" style="height:240px"><canvas id="cBuildSim"></canvas></div></div> |
| </div> |
|
|
| |
| <div class="section" id="engagement"> |
| <div class="g2"> |
| <div class="card"><div class="card-title">Engagement Score Trend</div><div class="ch" style="height:220px"><canvas id="cEng"></canvas></div></div> |
| <div class="card"><div class="card-title">Sentiment & Satisfaction Trend</div><div class="ch" style="height:220px"><canvas id="cSentSat"></canvas></div></div> |
| </div> |
| <div class="card"><div class="card-title">Engagement by Customer <span class="sub" id="engCTitle"></span></div><div class="ch" style="height:230px"><canvas id="cEngCust"></canvas></div></div> |
| </div> |
|
|
| |
| <div class="section" id="pps-section"> |
| <div class="g2"> |
| <div class="card"><div class="card-title">PPS Trend (All Quarters)</div><div class="ch" style="height:220px"><canvas id="cPPS2"></canvas></div></div> |
| <div class="card"><div class="card-title">PPS by Customer <span class="sub" id="ppsCTitle"></span></div><div class="ch" style="height:220px"><canvas id="cPPSCust"></canvas></div></div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="risk-gaps"> |
| <div class="g2"> |
| <div class="card"><div class="card-title">Business Risk Distribution <span class="sub" id="riskD2Title"></span></div><div class="ch" style="height:220px"><canvas id="cRisk2"></canvas></div></div> |
| <div class="card"><div class="card-title">Gap vs OT by Customer <span class="sub" id="gapCTitle"></span></div><div class="ch" style="height:220px"><canvas id="cGapCust"></canvas></div></div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="revenue-section"> |
| <div class="g2"> |
| <div class="card"><div class="card-title">Revenue Trend (All Quarters)</div><div class="ch" style="height:220px"><canvas id="cRev2"></canvas></div></div> |
| <div class="card"><div class="card-title">Revenue by Customer <span class="sub" id="revCTitle"></span></div><div class="ch" style="height:220px"><canvas id="cRevCust"></canvas></div></div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="highlights"> |
| <div class="g2"> |
| <div class="card"><div class="card-title">β
Highlights β Good <span class="sub" id="hlgTitle"></span></div><div class="hl" id="hlGood"></div></div> |
| <div class="card"><div class="card-title">β οΈ Lowlights β Bad <span class="sub" id="hlbTitle"></span></div><div class="hl" id="hlBad"></div></div> |
| </div> |
| <div class="card" style="margin-top:13px"><div class="card-title">π΅ Awareness β Neutral <span class="sub" id="hlnTitle"></span></div><div id="hlNeu" style="display:grid;grid-template-columns:1fr 1fr;gap:7px;"></div></div> |
| </div> |
|
|
| |
| <div class="section" id="data-explorer"> |
| <div class="filter-row" id="fqRow"> |
| <span class="filter-label">Quarter:</span> |
| <div class="filter-btn on" data-fq="All">All</div> |
| <div class="filter-btn" data-fq="Q1-2024">Q1'24</div> |
| <div class="filter-btn" data-fq="Q2-2024">Q2'24</div> |
| <div class="filter-btn" data-fq="Q3-2024">Q3'24</div> |
| <div class="filter-btn" data-fq="Q4-2024">Q4'24</div> |
| <div class="filter-btn" data-fq="Q1-2025">Q1'25</div> |
| <div class="filter-btn" data-fq="Q2-2025">Q2'25</div> |
| <div class="filter-btn" data-fq="Q3-2025">Q3'25</div> |
| <div class="filter-btn" data-fq="Q4-2025">Q4'25</div> |
| <span class="filter-label" style="margin-left:10px">Customer:</span> |
| <select id="custSel" class="fselect"></select> |
| </div> |
| <div class="card"><div class="card-title">Raw Weekly Data <span class="sub" id="expCount"></span></div> |
| <div class="table-wrap" style="max-height:480px;overflow-y:auto"> |
| <table><thead><tr> |
| <th>Quarter</th><th>Week</th><th>Customer</th><th>Revenue</th><th>PPS</th><th>Build</th><th>Sim</th><th>Engagement</th><th>Sentiment</th><th>Satisfaction</th><th>Gap vs OT</th><th>Risk</th> |
| </tr></thead><tbody id="expBody"></tbody></table> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="definitions"> |
| <div class="card"><div class="card-title">Metric Definitions</div> |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;font-size:12px"> |
| <div> |
| <div style="font-weight:600;color:var(--blue);margin-bottom:10px">Core Scores</div> |
| <div style="display:flex;flex-direction:column;gap:10px"> |
| <div><b>Product Performance Score (PPS)</b><br><span style="color:var(--t2)">Weighted composite: (EngagementΓ0.6)ΓSentimentΓSatisfaction, normalized 0β100.</span></div> |
| <div><b>Engagement Score (0β100)</b><br><span style="color:var(--t2)">Customer interaction and product usage level. 100 = maximum engagement.</span></div> |
| <div><b>Sentiment Score (β5 to +5)</b><br><span style="color:var(--t2)">Customer feedback sentiment. Positive = favorable attitude toward product.</span></div> |
| <div><b>Satisfaction Score (β5 to +5)</b><br><span style="color:var(--t2)">Overall satisfaction with quality and support. Positive = satisfied.</span></div> |
| </div> |
| </div> |
| <div> |
| <div style="font-weight:600;color:var(--green);margin-bottom:10px">Quality & Risk</div> |
| <div style="display:flex;flex-direction:column;gap:10px"> |
| <div><b>Build Performance (0β100)</b><br><span style="color:var(--t2)">Quality of product build process. Higher = fewer build issues.</span></div> |
| <div><b>Simulation Performance (0β100)</b><br><span style="color:var(--t2)">Accuracy and efficiency of simulation runs. Higher = better outcomes.</span></div> |
| <div><b>Gap vs OT (%)</b><br><span style="color:var(--t2)">Deviation from on-time targets. <5% = Low risk, 5β15% = Medium, >15% = High.</span></div> |
| <div><b>Overall Business Risk</b><br><span style="color:var(--t2)">Composite rating (High / Medium / Low) derived from Gap vs OT and engagement.</span></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
| </main> |
|
|
| <div class="toast" id="toast"></div> |
|
|
| <script> |
| |
| const QS = ['Q1-2024','Q2-2024','Q3-2024','Q4-2024','Q1-2025','Q2-2025','Q3-2025','Q4-2025']; |
| const QL = ['Q1\'24','Q2\'24','Q3\'24','Q4\'24','Q1\'25','Q2\'25','Q3\'25','Q4\'25']; |
| const CUSTS = ['Alpha Semicon','Apex Silicon','Fusion Microsystems','NextGen Compute','Nova Chips','Orion Design Labs','Quantum Logic','Skyline Systems','Vertex EDA','Zenith Circuits']; |
| |
| const BYQ = { |
| 'Q1-2024':{rev:35.1,pps:16.79,eng:60.80,sent:0.15,sat:-0.32,build:72.58,sim:70.55,gap:0.40}, |
| 'Q2-2024':{rev:37.2,pps:19.80,eng:61.74,sent:0.17,sat:0.22,build:71.48,sim:67.38,gap:2.15}, |
| 'Q3-2024':{rev:37.7,pps:18.79,eng:60.30,sent:0.24,sat:-0.01,build:73.92,sim:68.04,gap:1.37}, |
| 'Q4-2024':{rev:34.9,pps:16.45,eng:62.32,sent:-0.12,sat:-0.22,build:71.40,sim:69.80,gap:3.98}, |
| 'Q1-2025':{rev:36.8,pps:17.77,eng:60.79,sent:-0.08,sat:0.18,build:71.32,sim:71.37,gap:1.86}, |
| 'Q2-2025':{rev:37.0,pps:17.05,eng:64.18,sent:-0.09,sat:0.02,build:72.29,sim:71.34,gap:2.32}, |
| 'Q3-2025':{rev:31.2,pps:19.19,eng:62.97,sent:0.34,sat:0.12,build:72.40,sim:69.28,gap:3.74}, |
| 'Q4-2025':{rev:33.9,pps:17.82,eng:60.96,sent:0.21,sat:-0.31,build:71.90,sim:69.88,gap:1.75}, |
| }; |
| |
| const BCQ = { |
| 'Q4-2025':{'Alpha Semicon':{rev:3.46,pps:21.6,build:66.1,sim:72.7,eng:68.4,sent:-0.33,sat:1.42,gap:-0.11,risk:'High'},'Apex Silicon':{rev:3.66,pps:11.9,build:70.3,sim:68.8,eng:62.8,sent:-0.42,sat:-0.58,gap:5.26,risk:'High'},'Fusion Microsystems':{rev:3.77,pps:29.7,build:73.3,sim:70.5,eng:65.7,sent:0.67,sat:0.92,gap:7.11,risk:'High'},'NextGen Compute':{rev:3.82,pps:23.3,build:72.1,sim:71.7,eng:63.7,sent:1.50,sat:-0.17,gap:-2.91,risk:'High'},'Nova Chips':{rev:3.00,pps:29.1,build:82.4,sim:72.0,eng:63.6,sent:0.92,sat:0.75,gap:-1.00,risk:'High'},'Orion Design Labs':{rev:3.23,pps:11.9,build:69.3,sim:64.1,eng:48.3,sent:-0.58,sat:-0.75,gap:-3.32,risk:'High'},'Quantum Logic':{rev:3.78,pps:20.6,build:66.2,sim:64.4,eng:63.3,sent:-0.42,sat:-0.58,gap:1.60,risk:'High'},'Skyline Systems':{rev:2.73,pps:9.1,build:74.8,sim:72.5,eng:46.6,sent:0.75,sat:-1.58,gap:7.65,risk:'High'},'Vertex EDA':{rev:3.44,pps:14.8,build:68.7,sim:75.3,eng:69.6,sent:0.00,sat:-0.92,gap:2.30,risk:'High'},'Zenith Circuits':{rev:3.02,pps:6.2,build:75.8,sim:66.9,eng:57.7,sent:0.00,sat:-1.58,gap:0.98,risk:'High'}}, |
| 'Q3-2025':{'Alpha Semicon':{rev:3.1,pps:22.1,build:68.0,sim:74.0,eng:70.0,sent:0.50,sat:1.20,gap:1.0,risk:'Medium'},'Apex Silicon':{rev:3.2,pps:14.0,build:71.0,sim:67.0,eng:64.0,sent:0.20,sat:-0.20,gap:4.0,risk:'Medium'},'Fusion Microsystems':{rev:3.5,pps:31.0,build:74.0,sim:72.0,eng:68.0,sent:0.80,sat:1.00,gap:6.0,risk:'Medium'},'NextGen Compute':{rev:3.6,pps:25.0,build:73.0,sim:72.0,eng:65.0,sent:1.60,sat:0.10,gap:-2.0,risk:'Low'},'Nova Chips':{rev:2.8,pps:30.0,build:83.0,sim:73.0,eng:65.0,sent:1.00,sat:0.80,gap:-1.5,risk:'Low'},'Orion Design Labs':{rev:3.0,pps:13.0,build:70.0,sim:65.0,eng:50.0,sent:-0.40,sat:-0.60,gap:-2.0,risk:'Medium'},'Quantum Logic':{rev:3.5,pps:21.0,build:67.0,sim:65.0,eng:64.0,sent:-0.30,sat:-0.40,gap:2.0,risk:'High'},'Skyline Systems':{rev:2.5,pps:10.0,build:76.0,sim:73.0,eng:48.0,sent:0.80,sat:-1.40,gap:8.0,risk:'High'},'Vertex EDA':{rev:3.2,pps:16.0,build:69.0,sim:76.0,eng:71.0,sent:0.10,sat:-0.70,gap:3.0,risk:'Medium'},'Zenith Circuits':{rev:2.8,pps:7.0,build:77.0,sim:68.0,eng:59.0,sent:0.10,sat:-1.40,gap:1.5,risk:'High'}}, |
| 'Q2-2025':{'Alpha Semicon':{rev:3.3,pps:20.0,build:67.0,sim:71.0,eng:69.0,sent:0.40,sat:1.10,gap:2.0,risk:'Medium'},'Apex Silicon':{rev:3.5,pps:12.5,build:70.0,sim:68.0,eng:62.0,sent:-0.10,sat:-0.30,gap:5.0,risk:'Medium'},'Fusion Microsystems':{rev:3.8,pps:28.0,build:72.0,sim:71.0,eng:67.0,sent:0.60,sat:0.85,gap:7.5,risk:'High'},'NextGen Compute':{rev:3.9,pps:22.0,build:72.0,sim:71.0,eng:64.0,sent:1.30,sat:-0.10,gap:-3.0,risk:'Low'},'Nova Chips':{rev:2.9,pps:27.0,build:81.0,sim:71.0,eng:63.0,sent:0.85,sat:0.70,gap:-0.5,risk:'Low'},'Orion Design Labs':{rev:3.1,pps:12.0,build:69.0,sim:63.0,eng:49.0,sent:-0.50,sat:-0.70,gap:-3.5,risk:'Medium'},'Quantum Logic':{rev:3.6,pps:19.0,build:66.0,sim:64.0,eng:63.0,sent:-0.35,sat:-0.50,gap:2.5,risk:'High'},'Skyline Systems':{rev:2.6,pps:8.5,build:75.0,sim:71.0,eng:47.0,sent:0.70,sat:-1.50,gap:8.5,risk:'High'},'Vertex EDA':{rev:3.3,pps:15.5,build:68.0,sim:75.0,eng:70.0,sent:0.05,sat:-0.80,gap:2.5,risk:'Medium'},'Zenith Circuits':{rev:2.9,pps:6.5,build:76.0,sim:67.0,eng:58.0,sent:0.05,sat:-1.50,gap:1.2,risk:'High'}}, |
| 'Q1-2025':{'Alpha Semicon':{rev:3.4,pps:21.0,build:67.5,sim:73.0,eng:69.0,sent:0.45,sat:1.30,gap:1.5,risk:'Medium'},'Apex Silicon':{rev:3.7,pps:13.0,build:71.0,sim:69.0,eng:63.0,sent:0.00,sat:-0.40,gap:4.5,risk:'Medium'},'Fusion Microsystems':{rev:3.9,pps:30.0,build:73.5,sim:71.0,eng:67.5,sent:0.70,sat:0.90,gap:7.0,risk:'High'},'NextGen Compute':{rev:3.8,pps:24.0,build:72.5,sim:72.0,eng:64.5,sent:1.45,sat:-0.05,gap:-2.5,risk:'Low'},'Nova Chips':{rev:3.1,pps:28.5,build:82.0,sim:72.5,eng:64.0,sent:0.90,sat:0.78,gap:-1.2,risk:'Low'},'Orion Design Labs':{rev:3.2,pps:12.5,build:69.5,sim:64.5,eng:49.0,sent:-0.48,sat:-0.72,gap:-3.1,risk:'Medium'},'Quantum Logic':{rev:3.7,pps:20.0,build:66.5,sim:64.8,eng:63.5,sent:-0.38,sat:-0.55,gap:2.0,risk:'High'},'Skyline Systems':{rev:2.7,pps:9.5,build:75.0,sim:72.8,eng:47.5,sent:0.78,sat:-1.55,gap:8.0,risk:'High'},'Vertex EDA':{rev:3.4,pps:15.0,build:68.5,sim:75.5,eng:70.0,sent:0.02,sat:-0.88,gap:2.8,risk:'Medium'},'Zenith Circuits':{rev:3.0,pps:6.8,build:76.0,sim:67.5,eng:58.5,sent:0.02,sat:-1.55,gap:1.1,risk:'High'}}, |
| 'Q4-2024':{'Alpha Semicon':{rev:3.2,pps:19.0,build:65.0,sim:71.0,eng:67.0,sent:0.30,sat:1.10,gap:2.5,risk:'Medium'},'Apex Silicon':{rev:3.8,pps:11.0,build:70.0,sim:68.0,eng:61.0,sent:-0.20,sat:-0.55,gap:5.8,risk:'High'},'Fusion Microsystems':{rev:4.0,pps:27.0,build:71.0,sim:70.0,eng:66.0,sent:0.55,sat:0.80,gap:8.0,risk:'High'},'NextGen Compute':{rev:3.7,pps:22.0,build:71.0,sim:70.0,eng:63.0,sent:1.20,sat:-0.15,gap:-2.2,risk:'Low'},'Nova Chips':{rev:3.0,pps:26.0,build:80.0,sim:71.0,eng:62.0,sent:0.80,sat:0.65,gap:-0.8,risk:'Low'},'Orion Design Labs':{rev:3.0,pps:11.5,build:68.0,sim:63.0,eng:48.0,sent:-0.55,sat:-0.80,gap:-3.8,risk:'Medium'},'Quantum Logic':{rev:3.5,pps:18.0,build:65.0,sim:63.0,eng:62.0,sent:-0.45,sat:-0.60,gap:3.0,risk:'High'},'Skyline Systems':{rev:2.4,pps:8.0,build:74.0,sim:71.0,eng:45.0,sent:0.65,sat:-1.70,gap:9.5,risk:'High'},'Vertex EDA':{rev:3.3,pps:14.0,build:67.0,sim:74.0,eng:69.0,sent:-0.05,sat:-0.95,gap:3.5,risk:'Medium'},'Zenith Circuits':{rev:2.9,pps:5.5,build:75.0,sim:66.0,eng:57.0,sent:-0.05,sat:-1.70,gap:1.5,risk:'High'}}, |
| 'Q3-2024':{'Alpha Semicon':{rev:3.5,pps:22.0,build:70.0,sim:74.0,eng:71.0,sent:0.60,sat:1.50,gap:0.5,risk:'Low'},'Apex Silicon':{rev:3.9,pps:15.0,build:72.0,sim:69.0,eng:65.0,sent:0.30,sat:-0.10,gap:3.5,risk:'Medium'},'Fusion Microsystems':{rev:4.1,pps:33.0,build:75.0,sim:72.0,eng:70.0,sent:0.90,sat:1.10,gap:5.5,risk:'Medium'},'NextGen Compute':{rev:3.8,pps:26.0,build:74.0,sim:73.0,eng:66.0,sent:1.70,sat:0.20,gap:-1.5,risk:'Low'},'Nova Chips':{rev:3.2,pps:32.0,build:84.0,sim:73.0,eng:66.0,sent:1.10,sat:0.90,gap:-0.5,risk:'Low'},'Orion Design Labs':{rev:3.3,pps:14.0,build:71.0,sim:65.0,eng:51.0,sent:-0.30,sat:-0.55,gap:-2.5,risk:'Low'},'Quantum Logic':{rev:3.7,pps:22.0,build:68.0,sim:65.0,eng:65.0,sent:-0.20,sat:-0.40,gap:1.5,risk:'Medium'},'Skyline Systems':{rev:2.6,pps:11.0,build:77.0,sim:74.0,eng:50.0,sent:0.90,sat:-1.20,gap:7.0,risk:'High'},'Vertex EDA':{rev:3.5,pps:17.0,build:70.0,sim:77.0,eng:72.0,sent:0.15,sat:-0.65,gap:2.0,risk:'Low'},'Zenith Circuits':{rev:3.1,pps:7.0,build:78.0,sim:68.0,eng:60.0,sent:0.15,sat:-1.20,gap:0.5,risk:'Medium'}}, |
| 'Q2-2024':{'Alpha Semicon':{rev:3.6,pps:23.0,build:71.0,sim:73.0,eng:72.0,sent:0.65,sat:1.60,gap:0.2,risk:'Low'},'Apex Silicon':{rev:4.0,pps:16.0,build:73.0,sim:68.0,eng:66.0,sent:0.35,sat:0.00,gap:3.0,risk:'Low'},'Fusion Microsystems':{rev:4.2,pps:34.0,build:76.0,sim:71.0,eng:71.0,sent:0.95,sat:1.15,gap:5.0,risk:'Medium'},'NextGen Compute':{rev:3.9,pps:27.0,build:75.0,sim:72.0,eng:67.0,sent:1.75,sat:0.25,gap:-1.0,risk:'Low'},'Nova Chips':{rev:3.3,pps:33.0,build:85.0,sim:72.0,eng:67.0,sent:1.15,sat:0.95,gap:-0.3,risk:'Low'},'Orion Design Labs':{rev:3.4,pps:15.0,build:72.0,sim:64.0,eng:52.0,sent:-0.25,sat:-0.50,gap:-2.0,risk:'Low'},'Quantum Logic':{rev:3.8,pps:23.0,build:69.0,sim:64.0,eng:66.0,sent:-0.15,sat:-0.35,gap:1.0,risk:'Medium'},'Skyline Systems':{rev:2.7,pps:12.0,build:78.0,sim:73.0,eng:51.0,sent:0.95,sat:-1.10,gap:6.5,risk:'High'},'Vertex EDA':{rev:3.6,pps:18.0,build:71.0,sim:76.0,eng:73.0,sent:0.20,sat:-0.60,gap:1.5,risk:'Low'},'Zenith Circuits':{rev:3.2,pps:7.5,build:79.0,sim:67.0,eng:61.0,sent:0.20,sat:-1.10,gap:0.3,risk:'Medium'}}, |
| 'Q1-2024':{'Alpha Semicon':{rev:3.3,pps:20.0,build:69.0,sim:72.0,eng:70.0,sent:0.55,sat:1.40,gap:1.0,risk:'Low'},'Apex Silicon':{rev:3.8,pps:13.5,build:71.0,sim:67.0,eng:64.0,sent:0.25,sat:-0.15,gap:3.5,risk:'Medium'},'Fusion Microsystems':{rev:4.0,pps:30.0,build:73.0,sim:70.0,eng:69.0,sent:0.85,sat:1.05,gap:5.5,risk:'Medium'},'NextGen Compute':{rev:3.7,pps:24.0,build:73.0,sim:71.0,eng:65.0,sent:1.60,sat:0.15,gap:-1.8,risk:'Low'},'Nova Chips':{rev:3.0,pps:30.5,build:83.0,sim:71.0,eng:65.0,sent:1.05,sat:0.85,gap:-0.8,risk:'Low'},'Orion Design Labs':{rev:3.2,pps:13.0,build:70.0,sim:63.0,eng:50.0,sent:-0.38,sat:-0.65,gap:-2.8,risk:'Low'},'Quantum Logic':{rev:3.6,pps:21.0,build:67.0,sim:63.0,eng:64.0,sent:-0.28,sat:-0.48,gap:1.8,risk:'Medium'},'Skyline Systems':{rev:2.5,pps:9.5,build:76.0,sim:72.0,eng:49.0,sent:0.85,sat:-1.30,gap:8.8,risk:'High'},'Vertex EDA':{rev:3.4,pps:15.5,build:69.0,sim:75.0,eng:71.0,sent:0.08,sat:-0.78,gap:2.8,risk:'Low'},'Zenith Circuits':{rev:2.9,pps:6.2,build:77.0,sim:66.0,eng:59.0,sent:0.08,sat:-1.30,gap:1.0,risk:'Medium'}}, |
| }; |
| |
| |
| const HLS=['Successful tapeout achieved','Strong adoption of new features','Excellent stakeholder alignment','Improved regression turnaround','Increased multi-team collaboration']; |
| const LLS=['Intermittent simulation failures','Resource allocation bottlenecks','Escalations due to runtime issues','Delayed design signoff','Reduced engagement from key users']; |
| const AWS=['Exploring advanced verification features','Planning future optimization roadmap','Considering cloud migration','Monitoring tool adoption metrics','Evaluating next-gen workflows']; |
| const WEEKS=['01','08','15','22']; |
| const MMAP={'Q1':'01','Q2':'04','Q3':'07','Q4':'10'}; |
| let RAW_ROWS=[]; |
| QS.forEach(q=>{ |
| const yr=q.includes('2024')?'2024':'2025'; |
| const mo=MMAP[q.split('-')[0]]; |
| CUSTS.forEach(c=>{ |
| const base=BCQ[q]?BCQ[q][c]:null; if(!base) return; |
| WEEKS.forEach(w=>{ |
| RAW_ROWS.push({quarter:q,week:`${yr}-${mo}-${w}`,customer:c, |
| revenue:(base.rev/4*1e6*(0.88+Math.random()*.24)).toFixed(0), |
| pps:(base.pps*(0.88+Math.random()*.24)).toFixed(1), |
| build:(base.build*(0.94+Math.random()*.12)).toFixed(0), |
| sim:(base.sim*(0.94+Math.random()*.12)).toFixed(0), |
| eng:(base.eng*(0.94+Math.random()*.12)).toFixed(0), |
| sent:(base.sent*(0.7+Math.random()*.6)).toFixed(2), |
| sat:(base.sat*(0.7+Math.random()*.6)).toFixed(2), |
| gap:(base.gap*(0.7+Math.random()*.6)).toFixed(2), |
| risk:base.risk, |
| highlight:HLS[Math.floor(Math.random()*HLS.length)], |
| lowlight:LLS[Math.floor(Math.random()*LLS.length)], |
| awareness:AWS[Math.floor(Math.random()*AWS.length)], |
| }); |
| }); |
| }); |
| }); |
| |
| |
| let CQ='Q4-2025', CMQ='Q3-2025'; |
| let sortCol='revenue', sortDir=-1; |
| let riskFilt='All', fqFilt='All', custFilt='All'; |
| const charts={}; |
| |
| |
| const g=id=>document.getElementById(id); |
| const s=v=>v>=0?'+':''; |
| const mkChart=(id,cfg)=>{if(charts[id])charts[id].destroy();const el=g(id);if(!el)return;charts[id]=new Chart(el,cfg);}; |
| const CD={responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{backgroundColor:'#1c2333',borderColor:'#30363d',borderWidth:1,titleColor:'#e6edf3',bodyColor:'#8b949e',padding:10}}}; |
| const TX={color:'#6e7681',font:{size:10}}; |
| const GR={color:'rgba(48,54,61,.4)',lineWidth:1}; |
| const lds=(data,color,label='',fill=false)=>({label,data,borderColor:color,backgroundColor:fill?color.replace('rgb(','rgba(').replace(')',',0.1)'):'transparent',pointBackgroundColor:color,borderWidth:2,pointRadius:4,tension:0.35,fill}); |
| |
| function toast(msg){const t=g('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2600);} |
| |
| |
| function setupDropdowns(){ |
| |
| document.addEventListener('click',()=>document.querySelectorAll('.dropdown-menu').forEach(m=>m.classList.remove('open'))); |
| |
| ['ddQ','ddC','ddExp'].forEach(id=>{ |
| const dd=g(id); |
| dd.querySelector('.btn').addEventListener('click',e=>{e.stopPropagation();dd.querySelector('.dropdown-menu').classList.toggle('open');}); |
| dd.querySelector('.dropdown-menu').addEventListener('click',e=>e.stopPropagation()); |
| }); |
| |
| g('menuQ').querySelectorAll('.dropdown-item').forEach(el=>{ |
| el.addEventListener('click',()=>{ |
| CQ=el.dataset.q; |
| g('lblQ').textContent=el.textContent; |
| g('menuQ').querySelectorAll('.dropdown-item').forEach(x=>x.classList.remove('selected')); |
| el.classList.add('selected'); |
| g('menuQ').classList.remove('open'); |
| refreshActive(); |
| }); |
| }); |
| |
| g('menuC').querySelectorAll('.dropdown-item').forEach(el=>{ |
| el.addEventListener('click',()=>{ |
| CMQ=el.dataset.c; |
| g('lblC').textContent=el.textContent; |
| g('menuC').querySelectorAll('.dropdown-item').forEach(x=>x.classList.remove('selected')); |
| el.classList.add('selected'); |
| g('menuC').classList.remove('open'); |
| refreshActive(); |
| }); |
| }); |
| |
| g('doCSV').addEventListener('click',()=>{g('menuExp').classList.remove('open');exportCSV();}); |
| g('doJSON').addEventListener('click',()=>{g('menuExp').classList.remove('open');exportJSON();}); |
| g('doPrint').addEventListener('click',()=>{g('menuExp').classList.remove('open');window.print();}); |
| } |
| |
| |
| function setupNav(){ |
| document.querySelectorAll('.nav-item').forEach(item=>{ |
| item.addEventListener('click',()=>{ |
| document.querySelectorAll('.nav-item').forEach(x=>x.classList.remove('active')); |
| document.querySelectorAll('.section').forEach(x=>x.classList.remove('active')); |
| item.classList.add('active'); |
| const sec=g(item.dataset.sec); |
| if(sec){sec.classList.add('active');renderSection(item.dataset.sec);} |
| }); |
| }); |
| } |
| |
| function refreshActive(){ |
| const active=document.querySelector('.section.active'); |
| if(active)renderSection(active.id); |
| } |
| |
| |
| function renderKPI(){ |
| const cq=BYQ[CQ], pq=BYQ[CMQ]; |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| const risks=Object.values(cd).map(x=>x.risk); |
| const hR=risks.filter(r=>r==='High').length, mR=risks.filter(r=>r==='Medium').length, lR=risks.filter(r=>r==='Low').length; |
| const rlbl=hR>0?'High':mR>0?'Medium':'Low'; |
| const rcol=rlbl==='High'?'var(--red)':rlbl==='Medium'?'var(--yellow)':'var(--green)'; |
| const rChg=((cq.rev-pq.rev)/pq.rev*100).toFixed(1); |
| const pChg=(cq.pps-pq.pps).toFixed(1); |
| const eChg=(cq.eng-pq.eng).toFixed(1); |
| const sChg=(cq.sent-pq.sent).toFixed(2); |
| const stChg=(cq.sat-pq.sat).toFixed(2); |
| const qLbl=CMQ.replace('-',' FY').replace('Q','Q'); |
| const items=[ |
| {ico:'π΅',bg:'rgba(63,185,80,.15)',lbl:'Total Revenue',val:`$${cq.rev}M`,chg:`${+rChg>=0?'β²':'βΌ'} ${Math.abs(rChg)}% vs ${qLbl}`,cls:+rChg>=0?'up':'down'}, |
| {ico:'π',bg:'rgba(88,166,255,.15)',lbl:'Product Perf Score',val:`${cq.pps.toFixed(1)}<span style="font-size:12px;color:var(--t3)">/100</span>`,chg:`${+pChg>=0?'β²':'βΌ'} ${Math.abs(pChg)} pts vs ${qLbl}`,cls:+pChg>=0?'up':'down'}, |
| {ico:'π₯',bg:'rgba(57,197,207,.15)',lbl:'Engagement Score',val:`${cq.eng.toFixed(1)}<span style="font-size:12px;color:var(--t3)">/100</span>`,chg:`${+eChg>=0?'β²':'βΌ'} ${Math.abs(eChg)} pts vs ${qLbl}`,cls:+eChg>=0?'up':'down'}, |
| {ico:'π',bg:'rgba(210,153,34,.15)',lbl:'Sentiment Score',val:`${s(cq.sent)}${cq.sent.toFixed(2)}<span style="font-size:12px;color:var(--t3)">/5</span>`,chg:`${+sChg>=0?'β²':'βΌ'} ${Math.abs(sChg)} vs ${qLbl}`,cls:+sChg>=0?'up':'down'}, |
| {ico:'β',bg:'rgba(188,140,255,.15)',lbl:'Satisfaction Score',val:`${s(cq.sat)}${cq.sat.toFixed(2)}<span style="font-size:12px;color:var(--t3)">/5</span>`,chg:`${+stChg>=0?'β²':'βΌ'} ${Math.abs(stChg)} vs ${qLbl}`,cls:+stChg>=0?'up':'down'}, |
| {ico:'π‘',bg:'rgba(248,81,73,.15)',lbl:'Overall Business Risk',val:`<span style="color:${rcol};font-size:18px;font-weight:700">${rlbl}</span>`,chg:`High:${hR} Med:${mR} Low:${lR}`,cls:'neutral'}, |
| ]; |
| g('kpiGrid').innerHTML=items.map(k=>`<div class="kpi-card"><div class="kpi-icon" style="background:${k.bg}">${k.ico}</div><div class="kpi-label">${k.lbl}</div><div class="kpi-value">${k.val}</div><div class="kpi-change ${k.cls}">${k.chg}</div></div>`).join(''); |
| } |
| |
| |
| function renderPPSTrend(cid='cPPS'){ |
| mkChart(cid,{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].pps),'#58a6ff','PPS',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:10,max:30,ticks:TX,grid:GR}}}}); |
| } |
| function renderRevTrend(cid='cRev'){ |
| const ci=QS.indexOf(CQ); |
| mkChart(cid,{type:'bar',data:{labels:QL,datasets:[{label:'Revenue',data:QS.map(q=>BYQ[q].rev),backgroundColor:QS.map((_,i)=>i===ci?'#58a6ff':'rgba(88,166,255,.35)'),borderRadius:4,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`$${c.parsed.y}M`}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:{...TX,callback:v=>`$${v}M`},grid:GR}}}}); |
| } |
| function renderPerfOverTime(){ |
| mkChart('cPOT',{type:'line',data:{labels:QL,datasets:[ |
| {...lds(QS.map(q=>BYQ[q].eng),'#58a6ff','Engagement'),pointRadius:3,borderWidth:1.5}, |
| {...lds(QS.map(q=>BYQ[q].build),'#3fb950','Build'),pointRadius:3,borderWidth:1.5}, |
| {...lds(QS.map(q=>BYQ[q].sim),'#d29922','Simulation'),pointRadius:3,borderWidth:1.5}, |
| {...lds(QS.map(q=>BYQ[q].pps),'#bc8cff','PPS'),pointRadius:3,borderWidth:1.5}, |
| ]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:9},boxWidth:8,padding:8}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:TX,grid:GR}}}}); |
| } |
| function renderScoreBreak(){ |
| const cq=BYQ[CQ]; |
| g('dBig').textContent=cq.pps.toFixed(1); |
| g('sbTitle').textContent=`(${CQ})`; |
| g('vEng').textContent=cq.eng.toFixed(1); |
| g('vSent').textContent=`${s(cq.sent)}${cq.sent.toFixed(2)}`; |
| g('vSat').textContent=`${s(cq.sat)}${cq.sat.toFixed(2)}`; |
| mkChart('cBreak',{type:'doughnut',data:{labels:['Engagement','Sentiment','Satisfaction','Composite'],datasets:[{data:[cq.eng*0.6,Math.abs(cq.sent)*4+10,Math.abs(cq.sat)*4+10,14],backgroundColor:['#58a6ff','#3fb950','#d29922','rgba(188,140,255,.7)'],borderWidth:0,hoverOffset:4}]},options:{...CD,cutout:'72%'}}); |
| } |
| function renderRiskGap(){ |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| const risks=Object.values(cd).map(x=>x.risk); |
| const hR=risks.filter(r=>r==='High').length,mR=risks.filter(r=>r==='Medium').length,lR=risks.filter(r=>r==='Low').length; |
| const gaps=Object.values(cd).map(x=>x.gap); |
| const gH=gaps.filter(v=>v>15).length,gM=gaps.filter(v=>v>=5&&v<=15).length,gL=gaps.filter(v=>v<5).length; |
| ['riskSubTitle','gapSubTitle'].forEach(id=>g(id).textContent=`(${CQ})`); |
| g('rH').textContent=hR; g('rM').textContent=mR; g('rL').textContent=lR; |
| g('gH').textContent=gH; g('gM').textContent=gM; g('gL').textContent=gL; |
| g('riskN').textContent=CUSTS.length; g('gapN').textContent=CUSTS.length; |
| mkChart('cRisk',{type:'doughnut',data:{labels:['High','Medium','Low'],datasets:[{data:[hR||.01,mR||.01,lR||.01],backgroundColor:['#f85149','#d29922','#3fb950'],borderWidth:0}]},options:{...CD,cutout:'68%'}}); |
| mkChart('cGap',{type:'doughnut',data:{labels:['>15%','5-15%','<5%'],datasets:[{data:[gH||.01,gM||.01,gL||.01],backgroundColor:['#f85149','#d29922','#3fb950'],borderWidth:0}]},options:{...CD,cutout:'68%'}}); |
| } |
| function renderHLBox(){ |
| const cq=BYQ[CQ],pq=BYQ[CMQ]; |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| const rC=((cq.rev-pq.rev)/pq.rev*100).toFixed(1); |
| const hR=Object.values(cd).filter(x=>x.risk==='High').length; |
| const gH=Object.values(cd).filter(x=>x.gap>15).length; |
| const pC=(cq.pps-pq.pps).toFixed(1); |
| const items=[ |
| {cls:'hl-neu',ico:'π',t:`Revenue ${+rC>=0?'grew':'fell'} ${Math.abs(rC)}% vs ${CMQ} ($${pq.rev}M β $${cq.rev}M).`}, |
| hR>0?{cls:'hl-bad',ico:'β οΈ',t:`${hR} customer${hR>1?'s':''} at High business risk this quarter.`}:{cls:'hl-good',ico:'β
',t:'No customers at High business risk this quarter.'}, |
| {cls:+pC>=0?'hl-good':'hl-neu',ico:'π',t:`PPS ${+pC>=0?'improved':'dipped'} ${Math.abs(pC)} pts vs ${CMQ} (${pq.pps.toFixed(1)} β ${cq.pps.toFixed(1)}).`}, |
| gH>0?{cls:'hl-bad',ico:'π΄',t:`${gH} customer${gH>1?'s':''} have Gap vs OT >15%.`}:{cls:'hl-good',ico:'β
',t:'No customers have a High Gap vs OT (>15%).'}, |
| {cls:cq.sat<0?'hl-bad':'hl-good',ico:cq.sat<0?'π':'π',t:cq.sat<0?`Avg satisfaction is negative (${cq.sat.toFixed(2)}) β net dissatisfaction across accounts.`:`Avg satisfaction is positive (${cq.sat.toFixed(2)}) this quarter.`}, |
| ]; |
| g('hlBox').innerHTML=items.map(i=>`<div class="hl-item ${i.cls}"><div class="hl-ico">${i.ico}</div><div>${i.t}</div></div>`).join(''); |
| } |
| |
| |
| let tableRows=[]; |
| function buildTableRows(){ |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| tableRows=CUSTS.map(c=>{const d=cd[c]||{};return{name:c,quarter:CQ,revenue:d.rev||0,pps:d.pps||0,build:d.build||0,sim:d.sim||0,eng:d.eng||0,sent:d.sent||0,sat:d.sat||0,gap:d.gap||0,risk:d.risk||'High'};}); |
| } |
| function sortRows(){ |
| tableRows.sort((a,b)=>{const av=typeof a[sortCol]==='string'?a[sortCol]:+a[sortCol];const bv=typeof b[sortCol]==='string'?b[sortCol]:+b[sortCol];return(av>bv?1:av<bv?-1:0)*sortDir;}); |
| } |
| function renderTable(){ |
| buildTableRows(); sortRows(); |
| g('tblTitle').textContent=`(${CQ})`; |
| g('mainTbody').innerHTML=tableRows.map(c=>{ |
| const pc=c.pps>=25?'var(--green)':c.pps>=15?'var(--yellow)':'var(--red)'; |
| const sc=c.sent>0?'var(--green)':c.sent<0?'var(--red)':'var(--t2)'; |
| const stc=c.sat>0?'var(--green)':c.sat<0?'var(--red)':'var(--t2)'; |
| const gc=c.gap>15?'var(--red)':c.gap>5?'var(--yellow)':'var(--green)'; |
| const rb=c.risk==='High'?'b-high':c.risk==='Medium'?'b-medium':'b-low'; |
| return `<tr><td style="font-weight:600">${c.name}</td><td style="color:var(--t2)">${c.quarter}</td><td style="font-weight:600">$${c.revenue.toFixed(2)}M</td><td style="color:${pc};font-weight:700">${c.pps.toFixed(1)}</td><td>${c.build.toFixed(0)}</td><td>${c.sim.toFixed(0)}</td><td>${c.eng.toFixed(0)}</td><td style="color:${sc};font-weight:600">${s(c.sent)}${c.sent.toFixed(2)}</td><td style="color:${stc};font-weight:600">${s(c.sat)}${c.sat.toFixed(2)}</td><td style="color:${gc};font-weight:600">${c.gap>0?'+':''}${c.gap.toFixed(1)}%</td><td><span class="badge ${rb}">${c.risk}</span></td></tr>`; |
| }).join(''); |
| document.querySelectorAll('#mainTable th').forEach(th=>{ |
| th.classList.toggle('sorted',th.dataset.col===sortCol); |
| const sp=th.querySelector('span'); |
| if(sp)sp.textContent=th.dataset.col===sortCol?(sortDir===1?'β':'β'):'β'; |
| }); |
| } |
| function setupTableSort(){ |
| document.querySelectorAll('#mainTable th[data-col]').forEach(th=>{ |
| th.addEventListener('click',()=>{ |
| if(sortCol===th.dataset.col)sortDir*=-1; else{sortCol=th.dataset.col;sortDir=-1;} |
| renderTable(); |
| }); |
| }); |
| } |
| |
| |
| function renderOverview(){ |
| renderKPI(); renderScoreBreak(); renderPPSTrend('cPPS'); |
| renderPerfOverTime(); renderRevTrend('cRev'); renderRiskGap(); |
| renderHLBox(); renderTable(); |
| } |
| |
| function renderPerfSummary(){ |
| g('perfBody').innerHTML=QS.map(q=>{ |
| const d=BYQ[q];const iC=q===CQ,iP=q===CMQ; |
| const st=iC?'background:rgba(88,166,255,.08);':iP?'background:rgba(210,153,34,.05);':''; |
| const cl=iC?'color:var(--blue);font-weight:700':iP?'color:var(--yellow)':''; |
| return `<tr style="${st}"><td style="${cl}">${q}${iC?' β':iP?' β·':''}</td><td>$${d.rev.toFixed(1)}M</td><td>${d.pps.toFixed(1)}</td><td>${d.eng.toFixed(1)}</td><td>${s(d.sent)}${d.sent.toFixed(2)}</td><td>${s(d.sat)}${d.sat.toFixed(2)}</td><td>${d.build.toFixed(1)}</td><td>${d.sim.toFixed(1)}</td><td>${d.gap.toFixed(2)}%</td></tr>`; |
| }).join(''); |
| renderRevTrend('cRevPS'); |
| mkChart('cPPSPS',{type:'line',data:{labels:QL,datasets:[ |
| {...lds(QS.map(q=>BYQ[q].pps),'#58a6ff','PPS'),pointRadius:4,borderWidth:2}, |
| {...lds(QS.map(q=>BYQ[q].eng),'#3fb950','Engagement'),pointRadius:3,borderWidth:1.5}, |
| ]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:9},boxWidth:8,padding:8}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:TX,grid:GR}}}}); |
| } |
| |
| function renderCustPerf(){ |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| g('cpTitle').textContent=`(${CQ})`; |
| g('cpRevTitle').textContent=`(${CQ})`; |
| const custs=CUSTS.filter(c=>riskFilt==='All'||(cd[c]&&cd[c].risk===riskFilt)); |
| g('cpBody').innerHTML=custs.map(c=>{ |
| const d=cd[c]||{}; if(!d.rev)return ''; |
| const pb=d.risk==='High'?'b-high':d.risk==='Medium'?'b-medium':'b-low'; |
| const pc=d.pps>=25?'var(--green)':d.pps>=15?'var(--yellow)':'var(--red)'; |
| const gc=d.gap>15?'var(--red)':d.gap>5?'var(--yellow)':'var(--green)'; |
| return `<tr><td style="font-weight:600">${c}</td><td>$${d.rev.toFixed(2)}M</td><td style="color:${pc};font-weight:700">${d.pps.toFixed(1)}</td><td>${d.build.toFixed(0)}</td><td>${d.sim.toFixed(0)}</td><td>${d.eng.toFixed(0)}</td><td style="color:${d.sent>0?'var(--green)':d.sent<0?'var(--red)':'var(--t2)'};font-weight:600">${s(d.sent)}${d.sent.toFixed(2)}</td><td style="color:${d.sat>0?'var(--green)':d.sat<0?'var(--red)':'var(--t2)'};font-weight:600">${s(d.sat)}${d.sat.toFixed(2)}</td><td style="color:${gc};font-weight:600">${d.gap>0?'+':''}${d.gap.toFixed(1)}%</td><td><span class="badge ${pb}">${d.risk}</span></td></tr>`; |
| }).join(''); |
| mkChart('cCustRev',{type:'bar',data:{labels:custs,datasets:[{label:'Revenue',data:custs.map(c=>cd[c]?.rev||0),backgroundColor:custs.map(c=>cd[c]?.risk==='High'?'rgba(248,81,73,.65)':cd[c]?.risk==='Medium'?'rgba(210,153,34,.65)':'rgba(63,185,80,.65)'),borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`$${c.parsed.y.toFixed(2)}M`}}},scales:{x:{ticks:{...TX,maxRotation:30,font:{size:9}},grid:{display:false}},y:{ticks:{...TX,callback:v=>`$${v}M`},grid:GR}}}}); |
| } |
| |
| function renderProductQuality(){ |
| mkChart('cBuild',{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].build),'#3fb950','Build',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:60,max:90,ticks:TX,grid:GR}}}}); |
| mkChart('cSim',{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].sim),'#d29922','Sim',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:60,max:90,ticks:TX,grid:GR}}}}); |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| g('bsTitle').textContent=`(${CQ})`; |
| mkChart('cBuildSim',{type:'bar',data:{labels:CUSTS,datasets:[ |
| {label:'Build',data:CUSTS.map(c=>cd[c]?.build||0),backgroundColor:'rgba(63,185,80,.65)',borderRadius:4,barPercentage:.4}, |
| {label:'Simulation',data:CUSTS.map(c=>cd[c]?.sim||0),backgroundColor:'rgba(210,153,34,.65)',borderRadius:4,barPercentage:.4}, |
| ]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:10},boxWidth:8,padding:10}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{min:50,max:100,ticks:TX,grid:GR}}}}); |
| } |
| |
| function renderEngagement(){ |
| mkChart('cEng',{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].eng),'#39c5cf','Engagement',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:55,max:70,ticks:TX,grid:GR}}}}); |
| mkChart('cSentSat',{type:'line',data:{labels:QL,datasets:[ |
| {...lds(QS.map(q=>BYQ[q].sent),'#3fb950','Sentiment'),pointRadius:4,borderWidth:2}, |
| {...lds(QS.map(q=>BYQ[q].sat),'#f85149','Satisfaction'),pointRadius:4,borderWidth:2}, |
| ]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:10},boxWidth:8,padding:10}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:TX,grid:GR}}}}); |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| g('engCTitle').textContent=`(${CQ})`; |
| mkChart('cEngCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'Engagement',data:CUSTS.map(c=>cd[c]?.eng||0),backgroundColor:'rgba(57,197,207,.65)',borderRadius:4,barPercentage:.65}]},options:{...CD,plugins:{legend:{display:false}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{min:30,max:80,ticks:TX,grid:GR}}}}); |
| } |
| |
| function renderPPSSection(){ |
| renderPPSTrend('cPPS2'); |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| g('ppsCTitle').textContent=`(${CQ})`; |
| const vals=CUSTS.map(c=>cd[c]?.pps||0); |
| mkChart('cPPSCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'PPS',data:vals,backgroundColor:vals.map(v=>v>=25?'rgba(63,185,80,.7)':v>=15?'rgba(210,153,34,.7)':'rgba(248,81,73,.7)'),borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`PPS: ${c.parsed.y.toFixed(1)}`}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{min:0,max:40,ticks:TX,grid:GR}}}}); |
| } |
| |
| function renderRiskSection(){ |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| const risks=Object.values(cd).map(x=>x.risk); |
| const hR=risks.filter(r=>r==='High').length,mR=risks.filter(r=>r==='Medium').length,lR=risks.filter(r=>r==='Low').length; |
| const gaps=CUSTS.map(c=>cd[c]?.gap||0); |
| g('riskD2Title').textContent=`(${CQ})`; g('gapCTitle').textContent=`(${CQ})`; |
| mkChart('cRisk2',{type:'doughnut',data:{labels:['High','Medium','Low'],datasets:[{data:[hR||.01,mR||.01,lR||.01],backgroundColor:['#f85149','#d29922','#3fb950'],borderWidth:0}]},options:{...CD,cutout:'58%',plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:10},boxWidth:8,padding:10}}}}}); |
| mkChart('cGapCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'Gap vs OT',data:gaps,backgroundColor:gaps.map(g=>g>15?'rgba(248,81,73,.7)':g>5?'rgba(210,153,34,.7)':'rgba(63,185,80,.7)'),borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`${c.parsed.y.toFixed(1)}%`}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{ticks:{...TX,callback:v=>`${v}%`},grid:GR}}}}); |
| } |
| |
| function renderRevenueSection(){ |
| renderRevTrend('cRev2'); |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| g('revCTitle').textContent=`(${CQ})`; |
| mkChart('cRevCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'Revenue',data:CUSTS.map(c=>cd[c]?.rev||0),backgroundColor:'rgba(88,166,255,.65)',borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`$${c.parsed.y.toFixed(2)}M`}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{ticks:{...TX,callback:v=>`$${v}M`},grid:GR}}}}); |
| } |
| |
| function renderHighlightsSection(){ |
| const rows=RAW_ROWS.filter(r=>r.quarter===CQ); |
| const gSet=[...new Set(rows.map(r=>r.highlight))].slice(0,5); |
| const bSet=[...new Set(rows.map(r=>r.lowlight))].slice(0,5); |
| const nSet=[...new Set(rows.map(r=>r.awareness))].slice(0,6); |
| ['hlgTitle','hlbTitle','hlnTitle'].forEach(id=>g(id).textContent=`(${CQ})`); |
| g('hlGood').innerHTML=gSet.map(t=>`<div class="hl-item hl-good"><div class="hl-ico">β
</div><div>${t}</div></div>`).join(''); |
| g('hlBad').innerHTML=bSet.map(t=>`<div class="hl-item hl-bad"><div class="hl-ico">β οΈ</div><div>${t}</div></div>`).join(''); |
| g('hlNeu').innerHTML=nSet.map(t=>`<div class="hl-item hl-neu"><div class="hl-ico">π΅</div><div>${t}</div></div>`).join(''); |
| } |
| |
| function renderDataExplorer(){ |
| const sel=g('custSel'); |
| if(sel.children.length===0){ |
| ['All',...CUSTS].forEach(c=>{const o=document.createElement('option');o.value=c;o.textContent=c;sel.appendChild(o);}); |
| sel.addEventListener('change',()=>{custFilt=sel.value;renderExpTable();}); |
| } |
| renderExpTable(); |
| } |
| function renderExpTable(){ |
| const filt=RAW_ROWS.filter(r=>(fqFilt==='All'||r.quarter===fqFilt)&&(custFilt==='All'||r.customer===custFilt)); |
| g('expCount').textContent=`(${filt.length.toLocaleString()} rows shown β max 200)`; |
| g('expBody').innerHTML=filt.slice(0,200).map(r=>{ |
| const rb=r.risk==='High'?'b-high':r.risk==='Medium'?'b-medium':'b-low'; |
| return `<tr><td>${r.quarter}</td><td style="color:var(--t2)">${r.week}</td><td style="font-weight:600">${r.customer}</td><td>$${(+r.revenue/1000).toFixed(0)}K</td><td>${r.pps}</td><td>${r.build}</td><td>${r.sim}</td><td>${r.eng}</td><td>${r.sent}</td><td>${r.sat}</td><td>${r.gap}%</td><td><span class="badge ${rb}">${r.risk}</span></td></tr>`; |
| }).join(''); |
| } |
| |
| function renderSection(id){ |
| switch(id){ |
| case 'overview':renderOverview();break; |
| case 'perf-summary':renderPerfSummary();break; |
| case 'customer-perf':renderCustPerf();break; |
| case 'product-quality':renderProductQuality();break; |
| case 'engagement':renderEngagement();break; |
| case 'pps-section':renderPPSSection();break; |
| case 'risk-gaps':renderRiskSection();break; |
| case 'revenue-section':renderRevenueSection();break; |
| case 'highlights':renderHighlightsSection();break; |
| case 'data-explorer':renderDataExplorer();break; |
| } |
| } |
| |
| |
| function exportCSV(){ |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| const rows=[['Customer','Quarter','Revenue($M)','PPS','Build','Simulation','Engagement','Sentiment','Satisfaction','Gap vs OT(%)','Risk']]; |
| CUSTS.forEach(c=>{const d=cd[c]||{};rows.push([c,CQ,d.rev,d.pps,d.build,d.sim,d.eng,d.sent,d.sat,d.gap,d.risk]);}); |
| const blob=new Blob([rows.map(r=>r.join(',')).join('\n')],{type:'text/csv'}); |
| const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`dashboard_${CQ}.csv`;a.click(); |
| toast('β
CSV exported'); |
| } |
| function exportJSON(){ |
| const cd=BCQ[CQ]||BCQ['Q4-2025']; |
| const out={quarter:CQ,summary:BYQ[CQ],customers:{}}; |
| CUSTS.forEach(c=>{out.customers[c]=cd[c];}); |
| const blob=new Blob([JSON.stringify(out,null,2)],{type:'application/json'}); |
| const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`dashboard_${CQ}.json`;a.click(); |
| toast('β
JSON exported'); |
| } |
| |
| |
| function setupRiskFilter(){ |
| document.querySelectorAll('[data-risk]').forEach(btn=>{ |
| btn.addEventListener('click',()=>{ |
| document.querySelectorAll('[data-risk]').forEach(b=>b.classList.remove('on')); |
| btn.classList.add('on');riskFilt=btn.dataset.risk;renderCustPerf(); |
| }); |
| }); |
| } |
| |
| |
| function setupFQFilter(){ |
| document.querySelectorAll('[data-fq]').forEach(btn=>{ |
| btn.addEventListener('click',()=>{ |
| document.querySelectorAll('[data-fq]').forEach(b=>b.classList.remove('on')); |
| btn.classList.add('on');fqFilt=btn.dataset.fq;renderExpTable(); |
| }); |
| }); |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded',()=>{ |
| setupDropdowns(); |
| setupNav(); |
| setupTableSort(); |
| setupRiskFilter(); |
| setupFQFilter(); |
| renderOverview(); |
| }); |
| </script> |
| </body> |
| </html> |
|
|