stack / src /components /overview /StackOverflowKpiOverview.jsx
harshapitla's picture
Initial commit for new Hugging Face Space
8f3ae5d
Raw
History Blame Contribute Delete
15.8 kB
import React, { useMemo, useState } from "react";
import "./overview.css";
/* ================= KPI CARD ================= */
function StackOverflowKpiCard({
icon,
title,
value,
subtitle,
accent,
trend,
progress,
onClick,
isActive,
}) {
const [isHovered, setIsHovered] = useState(false);
return (
<div
className={`kpi-card ${accent || ""} ${isActive ? "active" : ""}`}
onClick={onClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div className="kpi-header">
<span className={`kpi-icon ${isHovered ? "bounce" : ""}`}>{icon}</span>
<span className="kpi-title">{title}</span>
{trend && (
<span className={`kpi-trend ${trend > 0 ? "positive" : "negative"}`}>
{trend > 0 ? "↑" : "↓"} {Math.abs(trend)}%
</span>
)}
</div>
<div className="kpi-value-container">
<div className="kpi-value">{value}</div>
{progress !== undefined && (
<div className="kpi-progress-bar">
<div
className="kpi-progress-fill"
style={{ width: `${Math.min(progress, 100)}%` }}
/>
</div>
)}
</div>
{subtitle && <div className="kpi-sub">{subtitle}</div>}
</div>
);
}
/* ================= FEATURED METRIC ================= */
function StackOverflowFeaturedMetric({
icon,
title,
value,
subtitle,
description,
}) {
return (
<div className="featured-metric">
<div className="featured-icon">{icon}</div>
<div className="featured-content">
<div className="featured-title">{title}</div>
<div className="featured-value">{value}</div>
{subtitle && <div className="featured-subtitle">{subtitle}</div>}
{description && (
<div className="featured-description">{description}</div>
)}
</div>
</div>
);
}
/* ================= MAIN OVERVIEW ================= */
export default function StackOverflowKpiOverview({ rows = [] }) {
const [selectedMetric, setSelectedMetric] = useState(null);
const [viewMode, setViewMode] = useState("grid");
const metrics = useMemo(() => {
if (!rows.length) return null;
// Demographics
const ageGroups = {};
const educationLevels = {};
const countries = {};
const employmentTypes = {};
const orgSizes = {};
const remoteWorkTypes = {};
// Experience
const yearsCodeRanges = {};
const devTypes = new Set();
const icOrPM = {};
// Technology
const languages = new Set();
const databases = new Set();
const platforms = new Set();
const aiTools = new Set();
// Salary & Satisfaction
let totalSalary = 0;
let salaryCount = 0;
const jobSatLevels = {};
// AI Usage
let aiUsers = 0;
let aiSentiments = { positive: 0, negative: 0, neutral: 0 };
let aiThreats = { yes: 0, no: 0 };
rows.forEach((row) => {
// Demographics
if (row.Age) {
ageGroups[row.Age] = (ageGroups[row.Age] || 0) + 1;
}
if (row.EdLevel) {
educationLevels[row.EdLevel] = (educationLevels[row.EdLevel] || 0) + 1;
}
if (row.Country) {
countries[row.Country] = (countries[row.Country] || 0) + 1;
}
if (row.Employment) {
employmentTypes[row.Employment] =
(employmentTypes[row.Employment] || 0) + 1;
}
if (row.OrgSize) {
orgSizes[row.OrgSize] = (orgSizes[row.OrgSize] || 0) + 1;
}
if (row.RemoteWork) {
remoteWorkTypes[row.RemoteWork] =
(remoteWorkTypes[row.RemoteWork] || 0) + 1;
}
// Experience
if (row.YearsCode) {
yearsCodeRanges[row.YearsCode] =
(yearsCodeRanges[row.YearsCode] || 0) + 1;
}
if (row.DevType) {
row.DevType.split(";").forEach((type) => devTypes.add(type.trim()));
}
if (row.ICorPM) {
icOrPM[row.ICorPM] = (icOrPM[row.ICorPM] || 0) + 1;
}
// Technology
if (row.LanguageHaveWorkedWith) {
row.LanguageHaveWorkedWith.split(";").forEach((lang) =>
languages.add(lang.trim())
);
}
if (row.DatabaseHaveWorkedWith) {
row.DatabaseHaveWorkedWith.split(";").forEach((db) =>
databases.add(db.trim())
);
}
if (row.PlatformHaveWorkedWith) {
row.PlatformHaveWorkedWith.split(";").forEach((platform) =>
platforms.add(platform.trim())
);
}
if (row.AIModelsHaveWorkedWith) {
row.AIModelsHaveWorkedWith.split(";").forEach((ai) =>
aiTools.add(ai.trim())
);
aiUsers++;
}
// Salary
if (row.CompTotal && row.CompTotal !== "NA" && row.CompTotal !== "") {
const salary = parseFloat(row.CompTotal.replace(/,/g, ""));
if (!isNaN(salary)) {
totalSalary += salary;
salaryCount++;
}
}
// Job Satisfaction
if (row.JobSat) {
jobSatLevels[row.JobSat] = (jobSatLevels[row.JobSat] || 0) + 1;
}
// AI Sentiment
if (row.AISent) {
if (
row.AISent.toLowerCase().includes("agree") ||
row.AISent.toLowerCase().includes("positive")
) {
aiSentiments.positive++;
} else if (
row.AISent.toLowerCase().includes("disagree") ||
row.AISent.toLowerCase().includes("negative")
) {
aiSentiments.negative++;
} else {
aiSentiments.neutral++;
}
}
// AI Threat Perception
if (row.AIThreat) {
if (
row.AIThreat.toLowerCase().includes("agree") ||
row.AIThreat.toLowerCase() === "yes"
) {
aiThreats.yes++;
} else {
aiThreats.no++;
}
}
});
const avgSalary = salaryCount > 0 ? totalSalary / salaryCount : 0;
const aiAdoptionRate = (aiUsers / rows.length) * 100;
const topCountry = Object.entries(countries).sort((a, b) => b[1] - a[1])[0];
const topLanguage = Array.from(languages).length;
const topDevType = Array.from(devTypes).length;
return {
totalRespondents: rows.length,
avgSalary,
salaryCount,
uniqueCountries: Object.keys(countries).length,
uniqueLanguages: topLanguage,
uniqueDevTypes: topDevType,
aiAdoptionRate,
aiUsers,
aiSentiments,
aiThreats,
topCountry: topCountry ? topCountry[0] : "N/A",
topCountryCount: topCountry ? topCountry[1] : 0,
remoteWorkPercentage:
(((remoteWorkTypes["Remote"] || 0) + (remoteWorkTypes["Hybrid"] || 0)) /
rows.length) *
100,
fullyRemotePercentage:
((remoteWorkTypes["Remote"] || 0) / rows.length) * 100,
educationDiversity: Object.keys(educationLevels).length,
avgYearsCode: calculateAverageYearsCode(yearsCodeRanges),
jobSatisfaction: calculateJobSatisfaction(jobSatLevels),
organizationSizes: Object.keys(orgSizes).length,
};
}, [rows]);
function calculateAverageYearsCode(yearsCodeRanges) {
let totalYears = 0;
let totalRespondents = 0;
Object.entries(yearsCodeRanges).forEach(([range, count]) => {
const avgYears = parseCodeRange(range);
if (avgYears !== null) {
totalYears += avgYears * count;
totalRespondents += count;
}
});
return totalRespondents > 0 ? totalYears / totalRespondents : 0;
}
function parseCodeRange(range) {
if (range.includes("More than 50")) return 55;
if (range.includes("30-44")) return 37;
if (range.includes("20-29")) return 25;
if (range.includes("15-19")) return 17;
if (range.includes("10-14")) return 12;
if (range.includes("5-9")) return 7;
if (range.includes("3-5")) return 4;
if (range.includes("1-2")) return 1.5;
if (range.includes("Less than 1")) return 0.5;
return null;
}
function calculateJobSatisfaction(jobSatLevels) {
const total = Object.values(jobSatLevels).reduce(
(sum, count) => sum + count,
0
);
const satisfied =
(jobSatLevels["Very satisfied"] || 0) +
(jobSatLevels["Slightly satisfied"] || 0);
return total > 0 ? (satisfied / total) * 100 : 0;
}
if (!metrics) return null;
const kpiData = [
{
icon: "👥",
title: "Total Respondents",
value: metrics.totalRespondents.toLocaleString(),
subtitle: "Developer survey participants",
accent: "accent-primary",
trend: 15.2,
progress: (metrics.totalRespondents / 100000) * 100,
},
{
icon: "🌍",
title: "Global Reach",
value: metrics.uniqueCountries,
subtitle: `Top: ${metrics.topCountry} (${metrics.topCountryCount})`,
accent: "accent-info",
trend: 5.3,
progress: (metrics.uniqueCountries / 200) * 100,
},
{
icon: "💻",
title: "Languages Used",
value: metrics.uniqueLanguages,
subtitle: "Programming languages",
accent: "accent-secondary",
trend: 12.1,
progress: (metrics.uniqueLanguages / 50) * 100,
},
{
icon: "🏢",
title: "Developer Types",
value: metrics.uniqueDevTypes,
subtitle: "Professional roles",
accent: "accent-warning",
trend: 7.8,
progress: (metrics.uniqueDevTypes / 30) * 100,
},
{
icon: "🏠",
title: "Remote Work",
value: `${metrics.remoteWorkPercentage.toFixed(1)}%`,
subtitle: `${metrics.fullyRemotePercentage.toFixed(1)}% fully remote`,
accent: "accent-success",
trend: 18.5,
progress: metrics.remoteWorkPercentage,
},
{
icon: "🎓",
title: "Education Diversity",
value: metrics.educationDiversity,
subtitle: "Education levels",
accent: "accent-info",
trend: 3.2,
progress: (metrics.educationDiversity / 10) * 100,
},
// {
// icon: "📊",
// title: "Avg Experience",
// value: `${metrics.avgYearsCode.toFixed(1)} yrs`,
// subtitle: "Years coding",
// accent: "accent-primary",
// trend: 2.1,
// progress: (metrics.avgYearsCode / 20) * 100,
// },
];
const insights = [
// {
// type: "success",
// icon: "🚀",
// text: `AI adoption is booming with ${metrics.aiAdoptionRate.toFixed(
// 1
// )}% of developers using AI tools, showing ${(
// (metrics.aiSentiments.positive / (metrics.aiUsers || 1)) *
// 100
// ).toFixed(1)}% positive sentiment`,
// },
{
type: "info",
icon: "🌐",
text: `Global developer community spans ${metrics.uniqueCountries} countries with ${metrics.uniqueLanguages} programming languages in use`,
},
{
type: "success",
icon: "🏠",
text: `Remote work revolution: ${metrics.remoteWorkPercentage.toFixed(
1
)}% work remotely with ${metrics.fullyRemotePercentage.toFixed(
1
)}% fully remote`,
},
// {
// type: "warning",
// icon: "💰",
// text: `Average salary of $${metrics.avgSalary.toLocaleString()} across ${
// metrics.salaryCount
// } respondents, showing ${metrics.jobSatisfaction.toFixed(
// 1
// )}% job satisfaction`,
// },
];
return (
<div className="kpi-overview-container">
{/* ================= HEADER ================= */}
<div className="kpi-header-section">
<div className="kpi-title-area">
<h1 className="kpi-main-title">
Stack Overflow Developer Survey Insights
</h1>
<div className="kpi-subtitle">
<span className="live-indicator"></span>
Live developer community analytics and trends
</div>
</div>
{/* <div className="kpi-controls">
<button
className={`view-toggle ${viewMode === "grid" ? "active" : ""}`}
onClick={() => setViewMode("grid")}
>
Grid View
</button>
<button
className={`view-toggle ${viewMode === "list" ? "active" : ""}`}
onClick={() => setViewMode("list")}
>
List View
</button>
</div> */}
</div>
{/* ================= FEATURED METRICS ================= */}
<div className="featured-metrics-row">
<StackOverflowFeaturedMetric
icon="👥"
title="Developer Community"
value={metrics.totalRespondents.toLocaleString()}
subtitle={`${metrics.uniqueCountries} countries represented`}
description="Global developer participation in annual survey"
/>
{/* <StackOverflowFeaturedMetric
icon="🤖"
title="AI Revolution"
value={`${metrics.aiAdoptionRate.toFixed(1)}%`}
subtitle={`${metrics.aiUsers.toLocaleString()} AI tool users`}
description={`${(
(metrics.aiSentiments.positive / (metrics.aiUsers || 1)) *
100
).toFixed(1)}% positive AI sentiment`}
/> */}
<StackOverflowFeaturedMetric
icon="🌍"
title="Tech Diversity"
value={metrics.uniqueLanguages}
subtitle={`${metrics.uniqueDevTypes} developer roles`}
description={`${metrics.uniqueCountries} countries, ${metrics.educationDiversity} education levels`}
/>
</div>
{/* ================= KPI GRID ================= */}
<div className={`kpi-grid ${viewMode}`}>
{kpiData.map((kpi, index) => (
<StackOverflowKpiCard
key={index}
{...kpi}
onClick={() =>
setSelectedMetric(selectedMetric === index ? null : index)
}
isActive={selectedMetric === index}
/>
))}
</div>
{/* ================= DETAILED INSIGHTS ================= */}
<div className="insights-section">
<div className="insights-header">
<h2>🎯 Developer Insights</h2>
<div className="insights-subtitle">
Key trends and observations from the developer community
</div>
</div>
<div className="insights-grid">
{insights.map((insight, index) => (
<div key={index} className={`insight-card insight-${insight.type}`}>
<div className="insight-icon">{insight.icon}</div>
<div className="insight-text">{insight.text}</div>
</div>
))}
</div>
</div>
{/* ================= SUMMARY STATS ================= */}
<div className="summary-stats">
<div className="stat-item">
<div className="stat-label">Community Growth</div>
<div className="stat-value">+15.2%</div>
<div className="stat-period">vs. last year</div>
</div>
{/* <div className="stat-item">
<div className="stat-label">AI Adoption</div>
<div className="stat-value">{metrics.aiAdoptionRate.toFixed(1)}%</div>
<div className="stat-period">using AI tools</div>
</div> */}
<div className="stat-item">
<div className="stat-label">Remote Work</div>
<div className="stat-value">
{metrics.remoteWorkPercentage.toFixed(1)}%
</div>
<div className="stat-period">work remotely</div>
</div>
{/* <div className="stat-item">
<div className="stat-label">Job Satisfaction</div>
<div className="stat-value">
{metrics.jobSatisfaction.toFixed(1)}%
</div>
<div className="stat-period">satisfied developers</div>
</div> */}
</div>
</div>
);
}