premium-stats-component

Pattern for creating premium statistics card components in JKKN COE Next.js application. This skill should be used when adding dashboard statistics, scorecards, metrics display, or summary cards to en

SKILL.md

GitHub

Premium Stats Component Skill

This skill provides patterns for creating consistent, visually appealing statistics card components in the JKKN COE application.

When to Use This Skill

Use this skill when:

  • Adding summary statistics to entity listing pages
  • Creating dashboard metric cards
  • Displaying count-based information (Total, Active, Inactive, New)
  • Building scorecards for data overview
  • Implementing loading states for statistics

File Locations (Following project-structure)

components/stats/
├── premium-[entity]-stats.tsx   # Entity-specific stats component

Component Structure

Basic Stats Component

import { Card, CardContent } from "@/components/ui/card"
import { Building2, CheckCircle, XCircle, TrendingUp, LucideIcon } from "lucide-react"

interface Entity {
  id: string
  is_active: boolean
  created_at: string
  [key: string]: any
}

interface StatConfig {
  label: string
  value: number
  icon: LucideIcon
  iconBg: string
  iconColor: string
  valueColor: string
}

interface PremiumEntityStatsProps {
  items: Entity[]
  loading?: boolean
}

export function PremiumEntityStats({ items = [], loading = false }: PremiumEntityStatsProps) {
  // Calculate statistics
  const total = items.length
  const active = items.filter((i) => i.is_active).length
  const inactive = items.filter((i) => !i.is_active).length
  const newThisMonth = items.filter((i) => {
    const itemDate = new Date(i.created_at)
    const currentDate = new Date()
    return (
      itemDate.getMonth() === currentDate.getMonth() &&
      itemDate.getFullYear() === currentDate.getFullYear()
    )
  }).length

  // Define stat configurations
  const stats: StatConfig[] = [
    {
      label: "Total Entities",
      value: total,
      icon: Building2,
      iconBg: "bg-blue-50 dark:bg-blue-900/20",
      iconColor: "text-blue-600 dark:text-blue-400",
      valueColor: "text-slate-900 dark:text-slate-100",
    },
    {
      label: "Active",
      value: active,
      icon: CheckCircle,
      iconBg: "bg-emerald-50 dark:bg-emerald-900/20",
      iconColor: "text-emerald-600 dark:text-emerald-400",
      valueColor: "text-emerald-600 dark:text-emerald-400",
    },
    {
      label: "Inactive",
      value: inactive,
      icon: XCircle,
      iconBg: "bg-red-50 dark:bg-red-900/20",
      iconColor: "text-red-600 dark:text-red-400",
      valueColor: "text-red-600 dark:text-red-400",
    },
    {
      label: "New This Month",
      value: newThisMonth,
      icon: TrendingUp,
      iconBg: "bg-purple-50 dark:bg-purple-900/20",
      iconColor: "text-purple-600 dark:text-purple-400",
      valueColor: "text-purple-600 dark:text-purple-400",
    },
  ]

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
      {stats.map((stat, index) => (
        <div key={index} className="card-premium-hover p-2">
          <Card>
            <CardContent className="p-3">
              <div className="flex items-center justify-between">
                <div>
                  <p className="text-sm text-slate-600 dark:text-slate-400">
                    {stat.label}
                  </p>
                  {loading ? (
                    <div className="h-9 w-20 bg-slate-200 dark:bg-slate-700 animate-pulse rounded mt-1" />
                  ) : (
                    <p
                      className={`text-3xl font-bold mt-1 font-grotesk ${stat.valueColor}`}
                      aria-label={`${stat.label}: ${stat.value}`}
                    >
                      {stat.value}
                    </p>
                  )}
                </div>
                <div
                  className={`h-12 w-12 rounded-xl flex items-center justify-center ${stat.iconBg}`}
                  aria-hidden="true"
                >
                  <stat.icon className={`h-6 w-6 ${stat.iconColor}`} />
                </div>
              </div>
            </CardContent>
          </Card>
        </div>
      ))}
    </div>
  )
}

Color Scheme Reference

Standard 4-Card Layout

Card Color Icon Use Case
Total Blue Building2/Package/Users Total count
Active Green/Emerald CheckCircle Active items
Inactive Red XCircle Inactive items
New Purple TrendingUp New this month

Color Classes

// Blue (Total)
iconBg: "bg-blue-50 dark:bg-blue-900/20"
iconColor: "text-blue-600 dark:text-blue-400"
valueColor: "text-slate-900 dark:text-slate-100"

// Green/Emerald (Active)
iconBg: "bg-emerald-50 dark:bg-emerald-900/20"
iconColor: "text-emerald-600 dark:text-emerald-400"
valueColor: "text-emerald-600 dark:text-emerald-400"

// Red (Inactive)
iconBg: "bg-red-50 dark:bg-red-900/20"
iconColor: "text-red-600 dark:text-red-400"
valueColor: "text-red-600 dark:text-red-400"

// Purple (New/Trending)
iconBg: "bg-purple-50 dark:bg-purple-900/20"
iconColor: "text-purple-600 dark:text-purple-400"
valueColor: "text-purple-600 dark:text-purple-400"

// Orange (Warning/Pending)
iconBg: "bg-orange-50 dark:bg-orange-900/20"
iconColor: "text-orange-600 dark:text-orange-400"
valueColor: "text-orange-600 dark:text-orange-400"

// Indigo (Special)
iconBg: "bg-indigo-50 dark:bg-indigo-900/20"
iconColor: "text-indigo-600 dark:text-indigo-400"
valueColor: "text-indigo-600 dark:text-indigo-400"

Icon Reference

Common icons for stats cards:

import {
  Building2,      // Institutions
  GraduationCap,  // Students/Education
  BookOpen,       // Courses
  Users,          // Users/Groups
  UserCheck,      // Active Users
  UserX,          // Inactive Users
  CheckCircle,    // Active/Complete
  XCircle,        // Inactive/Failed
  TrendingUp,     // Growth/New
  TrendingDown,   // Decline
  Calendar,       // Time-based
  Clock,          // Pending
  AlertTriangle,  // Warning
  Award,          // Achievements
  Package,        // Items/Products
  FileText,       // Documents
  Settings,       // Configuration
} from "lucide-react"

Usage in Page

import { PremiumEntityStats } from "@/components/stats/premium-entity-stats"

export default function EntityPage() {
  const [items, setItems] = useState<Entity[]>([])
  const [loading, setLoading] = useState(true)

  return (
    <div className="flex flex-1 flex-col gap-3 p-4 pt-0">
      {/* Breadcrumb */}

      {/* Premium Stats Cards */}
      <PremiumEntityStats items={items} loading={loading} />

      {/* Data Table Card */}
    </div>
  )
}

Responsive Grid Classes

// 4 cards (standard)
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">

// 3 cards
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">

// 5 cards
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">

// 6 cards (2 rows of 3)
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">

Loading State Pattern

{loading ? (
  <div className="h-9 w-20 bg-slate-200 dark:bg-slate-700 animate-pulse rounded mt-1" />
) : (
  <p className={`text-3xl font-bold mt-1 font-grotesk ${stat.valueColor}`}>
    {stat.value}
  </p>
)}

Custom Calculations

Percentage Calculation

const activePercentage = total > 0
  ? Math.round((active / total) * 100)
  : 0

// Display as "85%"
{
  label: "Active Rate",
  value: `${activePercentage}%`,
  // ...
}

Filtered Count

const pendingApproval = items.filter((i) =>
  i.status === 'pending' && i.submitted_at
).length

Date-Based Count

const newThisWeek = items.filter((i) => {
  const itemDate = new Date(i.created_at)
  const weekAgo = new Date()
  weekAgo.setDate(weekAgo.getDate() - 7)
  return itemDate >= weekAgo
}).length

Accessibility

<p
  className={`text-3xl font-bold mt-1 ${stat.valueColor}`}
  aria-label={`${stat.label}: ${stat.value}`}
>
  {stat.value}
</p>

<div
  className={`h-12 w-12 rounded-xl flex items-center justify-center ${stat.iconBg}`}
  aria-hidden="true"
>
  <stat.icon className={`h-6 w-6 ${stat.iconColor}`} />
</div>

Complete Example: Course Stats

See references/course-stats-example.md for a complete implementation with custom metrics.

Testing Checklist

  • Stats display correct counts
  • Loading state shows skeleton
  • Responsive layout on mobile/tablet/desktop
  • Dark mode colors correct
  • Accessibility labels present
  • New this month calculation accurate
  • Active/Inactive counts match data