diff --git a/src/components/VoteAnalytics.css b/src/components/VoteAnalytics.css new file mode 100644 index 0000000..ed953b0 --- /dev/null +++ b/src/components/VoteAnalytics.css @@ -0,0 +1,78 @@ +.vote-analytics-container { + margin-bottom: 2rem; +} + +.vote-analytics-container h3 { + margin-bottom: 1.5rem; + color: var(--color-gray100, #1d1127); + font-size: 1.75rem; + font-weight: 600; +} + +.analytics-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1.5rem; + margin-bottom: 1rem; +} + +.analytics-card { + background: white; + border: 1px solid var(--color-gray400); + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.2s ease, transform 0.05s ease; +} + +.analytics-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transform: translateY(-1px); +} + +.analytics-value { + font-size: 2.5rem; + font-weight: 700; + color: var(--color-blurple); + margin-bottom: 0.5rem; + line-height: 1; +} + +.analytics-label { + font-size: 1rem; + font-weight: 600; + color: var(--color-gray100); + line-height: 1.3; +} + +.analytics-subtext { + font-size: 0.85rem; + font-weight: 400; + color: var(--color-gray300); + margin-top: 0.25rem; + opacity: 0.8; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .analytics-grid { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + } + + .analytics-card { + padding: 1.25rem; + } + + .analytics-value { + font-size: 2rem; + } + + .analytics-label { + font-size: 0.9rem; + } + + .analytics-subtext { + font-size: 0.8rem; + } +} diff --git a/src/components/VoteAnalytics.js b/src/components/VoteAnalytics.js new file mode 100644 index 0000000..844a1cd --- /dev/null +++ b/src/components/VoteAnalytics.js @@ -0,0 +1,71 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const VoteAnalytics = ({data, awardCategories, projects, userCount}) => { + // Calculate total votes cast across all categories + const totalVotesCast = Object.values(data).reduce((total, categoryVotes) => { + return total + Object.values(categoryVotes).reduce((sum, votes) => sum + votes, 0); + }, 0); + + // Calculate total possible votes (each user gets 5 votes) + const totalPossibleVotes = userCount * 5; + + // Calculate participation percentage + const participationPercentage = + totalPossibleVotes > 0 ? ((totalVotesCast / totalPossibleVotes) * 100).toFixed(1) : 0; + + // Calculate votes per category + const votesPerCategory = Object.keys(awardCategories) + .map((categoryKey) => { + const categoryVotes = data[categoryKey] || {}; + const total = Object.values(categoryVotes).reduce((sum, votes) => sum + votes, 0); + return { + name: awardCategories[categoryKey].name, + total, + key: categoryKey, + }; + }) + .sort((a, b) => b.total - a.total); + + // Find most active category + const mostActiveCategory = votesPerCategory[0]; + + return ( +
| Rank | +Project | +Group | + {Object.keys(awardCategories).map((categoryKey) => ( +handleSort(categoryKey)} + className={getHeaderClassName(categoryKey)} + > + {awardCategories[categoryKey].name} + | + ))} +handleSort('totalVotes')} + className={getHeaderClassName('totalVotes')} + > + Totals + | +
|---|---|---|---|---|
| {/* Empty cell for rank column */} + | + Category Totals + | +{/* Empty cell for group column */} + {Object.keys(awardCategories).map((categoryKey) => { + const categoryVotes = Object.values(data[categoryKey] || {}).reduce( + (sum, votes) => sum + votes, + 0 + ); + return ( + | + {categoryVotes} + | + ); + })} ++ {grandTotal} + | +
| {index + 1} | ++ + {row.projectName} + + | +{row.groupName} | + {Object.keys(awardCategories).map((categoryKey) => ( ++ {row[categoryKey] || 0} + | + ))} ++ {row.totalVotes} + | +