CoolFace
Apppublic

harshapitla/stackoverflow

sourceHugging Facemitupdated 9mo agoView on Hugging Face
0likes
StackOverflowKpiOverview.jsx508 linesDownload Raw Back to overview
1import React, { useMemo, useState } from "react";2import "./overview.css";3 4/* ================= KPI CARD ================= */5function StackOverflowKpiCard({6  icon,7  title,8  value,9  subtitle,10  accent,11  trend,12  progress,13  onClick,14  isActive,15}) {16  const [isHovered, setIsHovered] = useState(false);17 18  return (19    <div20      className={`kpi-card ${accent || ""} ${isActive ? "active" : ""}`}21      onClick={onClick}22      onMouseEnter={() => setIsHovered(true)}23      onMouseLeave={() => setIsHovered(false)}24    >25      <div className="kpi-header">26        <span className={`kpi-icon ${isHovered ? "bounce" : ""}`}>{icon}</span>27        <span className="kpi-title">{title}</span>28        {trend && (29          <span className={`kpi-trend ${trend > 0 ? "positive" : "negative"}`}>30            {trend > 0 ? "↑" : "↓"} {Math.abs(trend)}%31          </span>32        )}33      </div>34      <div className="kpi-value-container">35        <div className="kpi-value">{value}</div>36        {progress !== undefined && (37          <div className="kpi-progress-bar">38            <div39              className="kpi-progress-fill"40              style={{ width: `${Math.min(progress, 100)}%` }}41            />42          </div>43        )}44      </div>45      {subtitle && <div className="kpi-sub">{subtitle}</div>}46    </div>47  );48}49 50/* ================= FEATURED METRIC ================= */51function StackOverflowFeaturedMetric({52  icon,53  title,54  value,55  subtitle,56  description,57}) {58  return (59    <div className="featured-metric">60      <div className="featured-icon">{icon}</div>61      <div className="featured-content">62        <div className="featured-title">{title}</div>63        <div className="featured-value">{value}</div>64        {subtitle && <div className="featured-subtitle">{subtitle}</div>}65        {description && (66          <div className="featured-description">{description}</div>67        )}68      </div>69    </div>70  );71}72 73/* ================= MAIN OVERVIEW ================= */74export default function StackOverflowKpiOverview({ rows = [] }) {75  const [selectedMetric, setSelectedMetric] = useState(null);76  const [viewMode, setViewMode] = useState("grid");77 78  const metrics = useMemo(() => {79    if (!rows.length) return null;80 81    // Demographics82    const ageGroups = {};83    const educationLevels = {};84    const countries = {};85    const employmentTypes = {};86    const orgSizes = {};87    const remoteWorkTypes = {};88 89    // Experience90    const yearsCodeRanges = {};91    const devTypes = new Set();92    const icOrPM = {};93 94    // Technology95    const languages = new Set();96    const databases = new Set();97    const platforms = new Set();98    const aiTools = new Set();99 100    // Salary & Satisfaction101    let totalSalary = 0;102    let salaryCount = 0;103    const jobSatLevels = {};104 105    // AI Usage106    let aiUsers = 0;107    let aiSentiments = { positive: 0, negative: 0, neutral: 0 };108    let aiThreats = { yes: 0, no: 0 };109 110    rows.forEach((row) => {111      // Demographics112      if (row.Age) {113        ageGroups[row.Age] = (ageGroups[row.Age] || 0) + 1;114      }115      if (row.EdLevel) {116        educationLevels[row.EdLevel] = (educationLevels[row.EdLevel] || 0) + 1;117      }118      if (row.Country) {119        countries[row.Country] = (countries[row.Country] || 0) + 1;120      }121      if (row.Employment) {122        employmentTypes[row.Employment] =123          (employmentTypes[row.Employment] || 0) + 1;124      }125      if (row.OrgSize) {126        orgSizes[row.OrgSize] = (orgSizes[row.OrgSize] || 0) + 1;127      }128      if (row.RemoteWork) {129        remoteWorkTypes[row.RemoteWork] =130          (remoteWorkTypes[row.RemoteWork] || 0) + 1;131      }132 133      // Experience134      if (row.YearsCode) {135        yearsCodeRanges[row.YearsCode] =136          (yearsCodeRanges[row.YearsCode] || 0) + 1;137      }138      if (row.DevType) {139        row.DevType.split(";").forEach((type) => devTypes.add(type.trim()));140      }141      if (row.ICorPM) {142        icOrPM[row.ICorPM] = (icOrPM[row.ICorPM] || 0) + 1;143      }144 145      // Technology146      if (row.LanguageHaveWorkedWith) {147        row.LanguageHaveWorkedWith.split(";").forEach((lang) =>148          languages.add(lang.trim())149        );150      }151      if (row.DatabaseHaveWorkedWith) {152        row.DatabaseHaveWorkedWith.split(";").forEach((db) =>153          databases.add(db.trim())154        );155      }156      if (row.PlatformHaveWorkedWith) {157        row.PlatformHaveWorkedWith.split(";").forEach((platform) =>158          platforms.add(platform.trim())159        );160      }161      if (row.AIModelsHaveWorkedWith) {162        row.AIModelsHaveWorkedWith.split(";").forEach((ai) =>163          aiTools.add(ai.trim())164        );165        aiUsers++;166      }167 168      // Salary169      if (row.CompTotal && row.CompTotal !== "NA" && row.CompTotal !== "") {170        const salary = parseFloat(row.CompTotal.replace(/,/g, ""));171        if (!isNaN(salary)) {172          totalSalary += salary;173          salaryCount++;174        }175      }176 177      // Job Satisfaction178      if (row.JobSat) {179        jobSatLevels[row.JobSat] = (jobSatLevels[row.JobSat] || 0) + 1;180      }181 182      // AI Sentiment183      if (row.AISent) {184        if (185          row.AISent.toLowerCase().includes("agree") ||186          row.AISent.toLowerCase().includes("positive")187        ) {188          aiSentiments.positive++;189        } else if (190          row.AISent.toLowerCase().includes("disagree") ||191          row.AISent.toLowerCase().includes("negative")192        ) {193          aiSentiments.negative++;194        } else {195          aiSentiments.neutral++;196        }197      }198 199      // AI Threat Perception200      if (row.AIThreat) {201        if (202          row.AIThreat.toLowerCase().includes("agree") ||203          row.AIThreat.toLowerCase() === "yes"204        ) {205          aiThreats.yes++;206        } else {207          aiThreats.no++;208        }209      }210    });211 212    const avgSalary = salaryCount > 0 ? totalSalary / salaryCount : 0;213    const aiAdoptionRate = (aiUsers / rows.length) * 100;214    const topCountry = Object.entries(countries).sort((a, b) => b[1] - a[1])[0];215    const topLanguage = Array.from(languages).length;216    const topDevType = Array.from(devTypes).length;217 218    return {219      totalRespondents: rows.length,220      avgSalary,221      salaryCount,222      uniqueCountries: Object.keys(countries).length,223      uniqueLanguages: topLanguage,224      uniqueDevTypes: topDevType,225      aiAdoptionRate,226      aiUsers,227      aiSentiments,228      aiThreats,229      topCountry: topCountry ? topCountry[0] : "N/A",230      topCountryCount: topCountry ? topCountry[1] : 0,231      remoteWorkPercentage:232        (((remoteWorkTypes["Remote"] || 0) + (remoteWorkTypes["Hybrid"] || 0)) /233          rows.length) *234        100,235      fullyRemotePercentage:236        ((remoteWorkTypes["Remote"] || 0) / rows.length) * 100,237      educationDiversity: Object.keys(educationLevels).length,238      avgYearsCode: calculateAverageYearsCode(yearsCodeRanges),239      jobSatisfaction: calculateJobSatisfaction(jobSatLevels),240      organizationSizes: Object.keys(orgSizes).length,241    };242  }, [rows]);243 244  function calculateAverageYearsCode(yearsCodeRanges) {245    let totalYears = 0;246    let totalRespondents = 0;247 248    Object.entries(yearsCodeRanges).forEach(([range, count]) => {249      const avgYears = parseCodeRange(range);250      if (avgYears !== null) {251        totalYears += avgYears * count;252        totalRespondents += count;253      }254    });255 256    return totalRespondents > 0 ? totalYears / totalRespondents : 0;257  }258 259  function parseCodeRange(range) {260    if (range.includes("More than 50")) return 55;261    if (range.includes("30-44")) return 37;262    if (range.includes("20-29")) return 25;263    if (range.includes("15-19")) return 17;264    if (range.includes("10-14")) return 12;265    if (range.includes("5-9")) return 7;266    if (range.includes("3-5")) return 4;267    if (range.includes("1-2")) return 1.5;268    if (range.includes("Less than 1")) return 0.5;269    return null;270  }271 272  function calculateJobSatisfaction(jobSatLevels) {273    const total = Object.values(jobSatLevels).reduce(274      (sum, count) => sum + count,275      0276    );277    const satisfied =278      (jobSatLevels["Very satisfied"] || 0) +279      (jobSatLevels["Slightly satisfied"] || 0);280    return total > 0 ? (satisfied / total) * 100 : 0;281  }282 283  if (!metrics) return null;284 285  const kpiData = [286    {287      icon: "👥",288      title: "Total Respondents",289      value: metrics.totalRespondents.toLocaleString(),290      subtitle: "Developer survey participants",291      accent: "accent-primary",292      trend: 15.2,293      progress: (metrics.totalRespondents / 100000) * 100,294    },295 296    {297      icon: "🌍",298      title: "Global Reach",299      value: metrics.uniqueCountries,300      subtitle: `Top: ${metrics.topCountry} (${metrics.topCountryCount})`,301      accent: "accent-info",302      trend: 5.3,303      progress: (metrics.uniqueCountries / 200) * 100,304    },305    {306      icon: "💻",307      title: "Languages Used",308      value: metrics.uniqueLanguages,309      subtitle: "Programming languages",310      accent: "accent-secondary",311      trend: 12.1,312      progress: (metrics.uniqueLanguages / 50) * 100,313    },314    {315      icon: "🏢",316      title: "Developer Types",317      value: metrics.uniqueDevTypes,318      subtitle: "Professional roles",319      accent: "accent-warning",320      trend: 7.8,321      progress: (metrics.uniqueDevTypes / 30) * 100,322    },323 324    {325      icon: "🏠",326      title: "Remote Work",327      value: `${metrics.remoteWorkPercentage.toFixed(1)}%`,328      subtitle: `${metrics.fullyRemotePercentage.toFixed(1)}% fully remote`,329      accent: "accent-success",330      trend: 18.5,331      progress: metrics.remoteWorkPercentage,332    },333    {334      icon: "🎓",335      title: "Education Diversity",336      value: metrics.educationDiversity,337      subtitle: "Education levels",338      accent: "accent-info",339      trend: 3.2,340      progress: (metrics.educationDiversity / 10) * 100,341    },342    // {343    //   icon: "📊",344    //   title: "Avg Experience",345    //   value: `${metrics.avgYearsCode.toFixed(1)} yrs`,346    //   subtitle: "Years coding",347    //   accent: "accent-primary",348    //   trend: 2.1,349    //   progress: (metrics.avgYearsCode / 20) * 100,350    // },351  ];352 353  const insights = [354    // {355    //   type: "success",356    //   icon: "🚀",357    //   text: `AI adoption is booming with ${metrics.aiAdoptionRate.toFixed(358    //     1359    //   )}% of developers using AI tools, showing ${(360    //     (metrics.aiSentiments.positive / (metrics.aiUsers || 1)) *361    //     100362    //   ).toFixed(1)}% positive sentiment`,363    // },364    {365      type: "info",366      icon: "🌐",367      text: `Global developer community spans ${metrics.uniqueCountries} countries with ${metrics.uniqueLanguages} programming languages in use`,368    },369    {370      type: "success",371      icon: "🏠",372      text: `Remote work revolution: ${metrics.remoteWorkPercentage.toFixed(373        1374      )}% work remotely with ${metrics.fullyRemotePercentage.toFixed(375        1376      )}% fully remote`,377    },378    // {379    //   type: "warning",380    //   icon: "💰",381    //   text: `Average salary of $${metrics.avgSalary.toLocaleString()} across ${382    //     metrics.salaryCount383    //   } respondents, showing ${metrics.jobSatisfaction.toFixed(384    //     1385    //   )}% job satisfaction`,386    // },387  ];388 389  return (390    <div className="kpi-overview-container">391      {/* ================= HEADER ================= */}392      <div className="kpi-header-section">393        <div className="kpi-title-area">394          <h1 className="kpi-main-title">395            Stack Overflow Developer Survey Insights396          </h1>397          <div className="kpi-subtitle">398            <span className="live-indicator">●</span>399            Live developer community analytics and trends400          </div>401        </div>402        {/* <div className="kpi-controls">403          <button404            className={`view-toggle ${viewMode === "grid" ? "active" : ""}`}405            onClick={() => setViewMode("grid")}406          >407            Grid View408          </button>409          <button410            className={`view-toggle ${viewMode === "list" ? "active" : ""}`}411            onClick={() => setViewMode("list")}412          >413            List View414          </button>415        </div> */}416      </div>417 418      {/* ================= FEATURED METRICS ================= */}419      <div className="featured-metrics-row">420        <StackOverflowFeaturedMetric421          icon="👥"422          title="Developer Community"423          value={metrics.totalRespondents.toLocaleString()}424          subtitle={`${metrics.uniqueCountries} countries represented`}425          description="Global developer participation in annual survey"426        />427        {/* <StackOverflowFeaturedMetric428          icon="🤖"429          title="AI Revolution"430          value={`${metrics.aiAdoptionRate.toFixed(1)}%`}431          subtitle={`${metrics.aiUsers.toLocaleString()} AI tool users`}432          description={`${(433            (metrics.aiSentiments.positive / (metrics.aiUsers || 1)) *434            100435          ).toFixed(1)}% positive AI sentiment`}436        /> */}437        <StackOverflowFeaturedMetric438          icon="🌍"439          title="Tech Diversity"440          value={metrics.uniqueLanguages}441          subtitle={`${metrics.uniqueDevTypes} developer roles`}442          description={`${metrics.uniqueCountries} countries, ${metrics.educationDiversity} education levels`}443        />444      </div>445 446      {/* ================= KPI GRID ================= */}447      <div className={`kpi-grid ${viewMode}`}>448        {kpiData.map((kpi, index) => (449          <StackOverflowKpiCard450            key={index}451            {...kpi}452            onClick={() =>453              setSelectedMetric(selectedMetric === index ? null : index)454            }455            isActive={selectedMetric === index}456          />457        ))}458      </div>459 460      {/* ================= DETAILED INSIGHTS ================= */}461      <div className="insights-section">462        <div className="insights-header">463          <h2>🎯 Developer Insights</h2>464          <div className="insights-subtitle">465            Key trends and observations from the developer community466          </div>467        </div>468        <div className="insights-grid">469          {insights.map((insight, index) => (470            <div key={index} className={`insight-card insight-${insight.type}`}>471              <div className="insight-icon">{insight.icon}</div>472              <div className="insight-text">{insight.text}</div>473            </div>474          ))}475        </div>476      </div>477 478      {/* ================= SUMMARY STATS ================= */}479      <div className="summary-stats">480        <div className="stat-item">481          <div className="stat-label">Community Growth</div>482          <div className="stat-value">+15.2%</div>483          <div className="stat-period">vs. last year</div>484        </div>485        {/* <div className="stat-item">486          <div className="stat-label">AI Adoption</div>487          <div className="stat-value">{metrics.aiAdoptionRate.toFixed(1)}%</div>488          <div className="stat-period">using AI tools</div>489        </div> */}490        <div className="stat-item">491          <div className="stat-label">Remote Work</div>492          <div className="stat-value">493            {metrics.remoteWorkPercentage.toFixed(1)}%494          </div>495          <div className="stat-period">work remotely</div>496        </div>497        {/* <div className="stat-item">498          <div className="stat-label">Job Satisfaction</div>499          <div className="stat-value">500            {metrics.jobSatisfaction.toFixed(1)}%501          </div>502          <div className="stat-period">satisfied developers</div>503        </div> */}504      </div>505    </div>506  );507}508