feat: Add GSAP animated counters and fix Team Schedule theme issues

- Created reusable AnimatedNumber component with GSAP
- Integrated animated counters in Dashboard (metrics, weather)
- Integrated animated counters in Leaderboard (podium, table)
- Fixed AdminSchedule dark mode styling with theme-aware classes
- Added responsive columns and functional dropdown to Team Schedule
- Implemented date range and status filters for Team Schedule
- Improved animation smoothness with expo.out easing
This commit is contained in:
Satyam
2026-02-16 13:48:04 +05:30
parent e0da94aa0c
commit ed79b266af
4 changed files with 471 additions and 69 deletions
+116
View File
@@ -0,0 +1,116 @@
import React, { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
/**
* AnimatedNumber - A reusable component that animates numbers from 0 to their target value using GSAP
* @param {number|string} value - The target value to animate to
* @param {number} duration - Animation duration in seconds (default: 2)
* @param {string} prefix - Optional prefix (e.g., "$")
* @param {string} suffix - Optional suffix (e.g., "%", "K", "M")
* @param {number} decimals - Number of decimal places (default: 0)
* @param {boolean} useLocaleString - Whether to format with commas (default: false)
* @param {string} className - Additional CSS classes
*/
const AnimatedNumber = ({
value,
duration = 2,
prefix = '',
suffix = '',
decimals = 0,
useLocaleString = false,
className = '',
delay = 0
}) => {
const numberRef = useRef(null);
const animationRef = useRef(null);
useEffect(() => {
if (!numberRef.current) return;
// Extract numeric value if it's a string like "$4.2M"
let numericValue = value;
let extractedPrefix = prefix;
let extractedSuffix = suffix;
if (typeof value === 'string') {
// Check for currency symbols
if (value.startsWith('$')) {
extractedPrefix = '$';
value = value.substring(1);
}
// Check for suffixes like M, K, B
const lastChar = value.slice(-1);
if (['K', 'M', 'B'].includes(lastChar.toUpperCase())) {
extractedSuffix = lastChar.toUpperCase();
value = value.slice(0, -1);
}
// Parse the numeric part
numericValue = parseFloat(value.replace(/,/g, ''));
}
// Handle non-numeric values
if (isNaN(numericValue)) {
numberRef.current.textContent = value;
return;
}
// Convert suffix multipliers to actual numbers
let multiplier = 1;
if (extractedSuffix === 'K') multiplier = 1000;
if (extractedSuffix === 'M') multiplier = 1000000;
if (extractedSuffix === 'B') multiplier = 1000000000;
const targetValue = numericValue * multiplier;
// Animate from 0 to target value
const obj = { val: 0 };
animationRef.current = gsap.to(obj, {
val: numericValue,
duration: duration,
delay: delay,
ease: 'expo.out',
onUpdate: () => {
if (numberRef.current) {
let displayValue = obj.val.toFixed(decimals);
if (useLocaleString) {
displayValue = parseFloat(displayValue).toLocaleString(undefined, {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
});
}
numberRef.current.textContent = `${extractedPrefix}${displayValue}${extractedSuffix}`;
}
},
onComplete: () => {
// Ensure final value is exact
if (numberRef.current) {
let displayValue = numericValue.toFixed(decimals);
if (useLocaleString) {
displayValue = parseFloat(displayValue).toLocaleString(undefined, {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
});
}
numberRef.current.textContent = `${extractedPrefix}${displayValue}${extractedSuffix}`;
}
}
});
return () => {
if (animationRef.current) {
animationRef.current.kill();
}
};
}, [value, duration, prefix, suffix, decimals, useLocaleString, delay]);
return <span ref={numberRef} className={className}>0</span>;
};
export default AnimatedNumber;