All files / frontend/src/components StatCard.tsx

100% Statements 65/65
100% Branches 5/5
100% Functions 1/1
100% Lines 65/65

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 901x 1x 1x                               1x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x   18x 18x 18x 18x 18x 18x 18x   18x 18x 18x 18x 18x 18x 18x 18x 1x 1x 1x   18x 3x 3x 3x 3x 3x 3x   3x 3x 3x 3x 3x 3x 3x 3x 3x 2x   3x   18x 18x 1x 1x 1x   18x 18x 18x   18x   1x  
import React from 'react';
import classNames from 'classnames';
import './StatCard.css';
 
export interface StatCardProps {
    title: string;
    value: string | number;
    icon?: string; // Phosphor icon class (e.g., "ph-activity")
    variant?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
    trend?: {
        value: number;
        direction: 'up' | 'down';
        label?: string;
    };
    subtitle?: string;
    className?: string;
}
 
const StatCard: React.FC<StatCardProps> = ({
    title,
    value,
    icon,
    variant = 'primary',
    trend,
    subtitle,
    className,
}) => {
    const variantClasses = {
        primary: 'stat-card-primary',
        success: 'stat-card-success',
        warning: 'stat-card-warning',
        danger: 'stat-card-danger',
        info: 'stat-card-info',
    };
 
    const iconColors = {
        primary: 'text-primary',
        success: 'text-success',
        warning: 'text-warning',
        danger: 'text-danger',
        info: 'text-info',
    };
 
    return (
        <div className={classNames('card stat-card', variantClasses[variant], className)}>
            <div className="card-body">
                <div className="stat-card-content">
                    <div className="stat-card-info">
                        <h6 className="stat-card-title text-muted mb-2">{title}</h6>
                        <h3 className="stat-card-value mb-0">{value}</h3>
                        {subtitle && (
                            <p className="stat-card-subtitle text-muted mb-0 mt-1">
                                {subtitle}
                            </p>
                        )}
                        {trend && (
                            <div className="stat-card-trend mt-2">
                                <span
                                    className={classNames('stat-trend', {
                                        'stat-trend-up': trend.direction === 'up',
                                        'stat-trend-down': trend.direction === 'down',
                                    })}
                                >
                                    <i
                                        className={classNames('ph', {
                                            'ph-trend-up': trend.direction === 'up',
                                            'ph-trend-down': trend.direction === 'down',
                                        })}
                                    ></i>
                                    {Math.abs(trend.value)}%
                                </span>
                                {trend.label && (
                                    <span className="text-muted ms-2">{trend.label}</span>
                                )}
                            </div>
                        )}
                    </div>
                    {icon && (
                        <div className="stat-card-icon">
                            <i className={classNames('ph', icon, iconColors[variant])}></i>
                        </div>
                    )}
                </div>
            </div>
        </div>
    );
};
 
export default StatCard;