Skip to main content

Overview

The Analytics component provides a comprehensive data visualization dashboard that displays key business metrics and order analytics. It uses Recharts for interactive visualizations and integrates with Redux to analyze pizza catalog and order history data. Location: src/components/dashboard/Analytics.tsx

Features

  • Revenue metrics: Total revenue, order count, and average order value
  • Price comparison chart: Horizontal bar chart showing all pizza prices
  • Category distribution: Pie chart visualizing order distribution by category
  • Responsive design: Adapts from mobile to desktop layouts
  • Empty states: Helpful messages when no data is available
  • Interactive tooltips: Hover states on chart elements
  • Framer Motion animations: Smooth hover effects on stat cards

Props

This component has no props. It reads data directly from Redux.

Redux State

Required State Slices

interface RootState {
  pizza: {
    pizzas: Pizza[];
  };
  order: {
    orderHistory: Order[];
  };
}

Redux Integration

import { useAppSelector } from '../../store/index.ts';

const { pizzas } = useAppSelector((state) => state.pizza);
const { orderHistory } = useAppSelector((state) => state.order);

Usage Example

import Analytics from './components/dashboard/Analytics';

function DashboardPage() {
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-4xl font-black mb-8">Analytics Dashboard</h1>
      <Analytics />
    </div>
  );
}

Component Structure

The component is organized into two main sections:
  1. Stats Overview: Three metric cards showing key performance indicators
  2. Charts Grid: Two-column layout with price chart and category distribution
<div className="space-y-8 mb-12">
  {/* Stats Overview */}
  <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
    {/* Revenue, Orders, Avg Order Value */}
  </div>

  {/* Charts Grid */}
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
    {/* Price Chart */}
    {/* Category Distribution */}
  </div>
</div>

Statistics Cards

Three animated stat cards display key metrics:

Total Revenue

const totalRevenue = orderHistory.reduce(
  (acc, order) => acc + order.finalTotal, 
  0
);
<motion.div 
  whileHover={{ y: -5 }}
  className="bg-white/60 backdrop-blur-md p-6 rounded-3xl border border-white/20 shadow-xl flex items-center gap-4"
>
  <div className="p-4 bg-orange-100 text-orange-600 rounded-2xl">
    <DollarSign size={24} />
  </div>
  <div>
    <p className="text-[10px] font-black uppercase tracking-widest text-gray-400">
      Total Revenue
    </p>
    <p className="text-2xl font-black text-gray-900">
      ${totalRevenue.toFixed(2)}
    </p>
  </div>
</motion.div>

Orders Confirmed

const totalOrders = orderHistory.length;
<motion.div 
  whileHover={{ y: -5 }}
  className="bg-white/60 backdrop-blur-md p-6 rounded-3xl border border-white/20 shadow-xl flex items-center gap-4"
>
  <div className="p-4 bg-blue-100 text-blue-600 rounded-2xl">
    <Activity size={24} />
  </div>
  <div>
    <p className="text-[10px] font-black uppercase tracking-widest text-gray-400">
      Orders Confirmed
    </p>
    <p className="text-2xl font-black text-gray-900">{totalOrders}</p>
  </div>
</motion.div>

Average Order Value

const avgOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
<motion.div 
  whileHover={{ y: -5 }}
  className="bg-white/60 backdrop-blur-md p-6 rounded-3xl border border-white/20 shadow-xl flex items-center gap-4"
>
  <div className="p-4 bg-green-100 text-green-600 rounded-2xl">
    <TrendingUp size={24} />
  </div>
  <div>
    <p className="text-[10px] font-black uppercase tracking-widest text-gray-400">
      Avg. Order Value
    </p>
    <p className="text-2xl font-black text-gray-900">
      ${avgOrderValue.toFixed(2)}
    </p>
  </div>
</motion.div>

Price Comparison Chart

Horizontal bar chart showing pizza prices sorted from highest to lowest:

Data Preparation

const priceData = pizzas.map(p => ({
  name: p.name,
  price: p.price
})).sort((a, b) => b.price - a.price);

Chart Implementation

<div className="bg-white/60 backdrop-blur-md p-8 rounded-4xl border border-white/20 shadow-2xl">
  <div className="flex items-center gap-3 mb-8">
    <Activity className="text-orange-600" size={24} />
    <h3 className="text-lg font-black uppercase tracking-tighter text-gray-900">
      Pizza Price Comparison
    </h3>
  </div>
  
  <div className="h-[300px] w-full">
    <ResponsiveContainer width="100%" height="100%">
      <BarChart data={priceData} layout="vertical" margin={{ left: 20 }}>
        <CartesianGrid strokeDasharray="3 3" horizontal={false} stroke="#e5e7eb" />
        
        <XAxis type="number" hide />
        
        <YAxis 
          dataKey="name" 
          type="category" 
          axisLine={false} 
          tickLine={false} 
          width={100} 
          tick={{ fontSize: 10, fontWeight: 700, fill: '#4b5563' }}
        />
        
        <Tooltip 
          cursor={{ fill: '#fff7ed' }}
          contentStyle={{ 
            borderRadius: '16px', 
            border: 'none', 
            boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)',
            fontSize: '12px',
            fontWeight: 800
          }}
        />
        
        <Bar dataKey="price" fill="#ea580c" radius={[0, 10, 10, 0]} barSize={20} />
      </BarChart>
    </ResponsiveContainer>
  </div>
</div>
Chart Configuration:
  • Layout: vertical for horizontal bars
  • Grid: Vertical lines only (horizontal={false})
  • Y-axis: Shows pizza names (100px width)
  • X-axis: Hidden (hide={true})
  • Bars: Orange fill (#ea580c) with rounded right corners
  • Tooltip: Styled with rounded corners and shadow

Category Distribution Chart

Pie chart visualizing order distribution by pizza category:

Data Preparation

const categoryCounts: Record<string, number> = {};

orderHistory.forEach(order => {
  order.items.forEach(item => {
    const cat = item.pizza.category;
    categoryCounts[cat] = (categoryCounts[cat] || 0) + item.quantity;
  });
});

const categoryData = Object.entries(categoryCounts).map(([name, value]) => ({
  name,
  value
}));
Data Structure:
  • Aggregates quantities across all order items
  • Groups by pizza category
  • Converts to array format for Recharts

Chart Implementation

<div className="bg-white/60 backdrop-blur-md p-8 rounded-4xl border border-white/20 shadow-2xl">
  <div className="flex items-center gap-3 mb-8">
    <PieChartIcon className="text-orange-600" size={24} />
    <h3 className="text-lg font-black uppercase tracking-tighter text-gray-900">
      Order Distribution
    </h3>
  </div>
  
  <div className="h-[300px] w-full">
    {categoryData.length > 0 ? (
      <ResponsiveContainer width="100%" height="100%">
        <PieChart>
          <Pie
            data={categoryData}
            cx="50%"
            cy="50%"
            innerRadius={60}
            outerRadius={100}
            paddingAngle={8}
            dataKey="value"
          >
            {categoryData.map((_entry, index) => (
              <Cell 
                key={`cell-${index}`} 
                fill={COLORS[index % COLORS.length]} 
                stroke="none" 
              />
            ))}
          </Pie>
          
          <Tooltip 
            contentStyle={{ 
              borderRadius: '16px', 
              border: 'none', 
              boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' 
            }}
          />
          
          <Legend 
            verticalAlign="bottom" 
            height={36} 
            iconType="circle"
            formatter={(value) => (
              <span className="text-[10px] font-black uppercase tracking-widest text-gray-500 ml-2">
                {value}
              </span>
            )}
          />
        </PieChart>
      </ResponsiveContainer>
    ) : (
      {/* Empty state */}
    )}
  </div>
</div>

Color Palette

const COLORS = ['#ea580c', '#f97316', '#fbbf24', '#f59e0b', '#dc2626'];
Orange-red gradient palette for visual consistency. Chart Configuration:
  • Type: Donut chart (innerRadius={60})
  • Outer radius: 100px
  • Padding angle: 8 degrees between segments
  • Colors: Cycled from COLORS array
  • Legend: Bottom-aligned with custom formatting

Empty State

Shown when no orders exist:
<div className="h-full flex flex-col items-center justify-center text-gray-400">
  <PieChartIcon size={48} strokeWidth={1} className="mb-4 opacity-20" />
  <p className="text-sm font-bold uppercase tracking-widest">No orders yet</p>
  <p className="text-[10px]">Analytics will appear here after your first order</p>
</div>

Recharts Integration

The component uses Recharts library for data visualization:
import { 
  BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, 
  PieChart, Pie, Cell, Legend
} from 'recharts';

Chart Components

ResponsiveContainer
component
Wrapper that makes charts responsive to container size
BarChart
component
Container for bar chart with configurable layout and margins
PieChart
component
Container for pie/donut charts
Tooltip
component
Interactive tooltip shown on hover
Legend
component
Chart legend with customizable formatting

Animations

Stat Card Hover

Framer Motion provides lift effect:
<motion.div whileHover={{ y: -5 }}>
Cards lift 5px upward on hover for tactile feedback.

Chart Animations

Recharts provides built-in animations:
  • Bars animate from left to right on mount
  • Pie segments animate with rotation
  • Tooltips fade in smoothly

Responsive Design

Stats Grid

grid-cols-1 md:grid-cols-3
  • Mobile: Stacked vertically
  • Tablet+: Three columns

Charts Grid

grid-cols-1 lg:grid-cols-2
  • Mobile/Tablet: Stacked vertically
  • Desktop: Two columns side-by-side

Chart Sizing

<div className="h-[300px] w-full">
  <ResponsiveContainer width="100%" height="100%">
Fixed height (300px) with 100% width for consistent proportions.

Visual Design

Glass Morphism Cards

bg-white/60 backdrop-blur-md border border-white/20 shadow-xl

Icon Backgrounds

Colored circular backgrounds for stat icons:
bg-orange-100 text-orange-600  /* Revenue */
bg-blue-100 text-blue-600      /* Orders */
bg-green-100 text-green-600    /* Avg Value */

Typography

  • Stat labels: text-[10px] font-black uppercase tracking-widest
  • Stat values: text-2xl font-black
  • Chart titles: text-lg font-black uppercase tracking-tighter

Performance Considerations

Data Processing

All calculations happen in the component:
// Computed on every render (consider useMemo for large datasets)
const priceData = pizzas.map(/* ... */).sort(/* ... */);
const categoryData = Object.entries(categoryCounts).map(/* ... */);
For production with large datasets, wrap in useMemo:
import { useMemo } from 'react';

const priceData = useMemo(() => 
  pizzas.map(p => ({ name: p.name, price: p.price }))
    .sort((a, b) => b.price - a.price),
  [pizzas]
);

Complete Example

Full dashboard page with navigation:
import { useState } from 'react';
import Analytics from './components/dashboard/Analytics';
import { useAppSelector } from './store';

function DashboardPage() {
  const orderHistory = useAppSelector((state) => state.order.orderHistory);
  
  return (
    <div className="min-h-screen bg-gradient-to-br from-orange-50 to-red-50">
      <div className="container mx-auto px-4 py-8">
        {/* Header */}
        <div className="mb-8">
          <h1 className="text-4xl font-black text-gray-900 mb-2">
            Analytics Dashboard
          </h1>
          <p className="text-gray-600">
            Viewing {orderHistory.length} confirmed orders
          </p>
        </div>
        
        {/* Analytics component */}
        <Analytics />
        
        {/* Additional sections */}
        <div className="mt-12">
          <h2 className="text-2xl font-black mb-6">Recent Orders</h2>
          {/* Order list */}
        </div>
      </div>
    </div>
  );
}

Data Flow

OrderSummary (confirms order)

addOrderToHistory (Redux action)

orderHistory state updated

Analytics component re-renders

Charts updated with new data

Accessibility

  • Semantic HTML: Proper heading hierarchy
  • ARIA labels: Charts have descriptive titles
  • Color contrast: Text meets WCAG standards
  • Keyboard navigation: Interactive chart elements are keyboard accessible
  • Screen reader support: Recharts provides basic ARIA support

Icon Usage

Lucide React icons throughout:
import { 
  TrendingUp,      // Avg order value
  PieChart as PieChartIcon,  // Category chart title
  DollarSign,      // Revenue
  Activity         // Orders count & price chart
} from 'lucide-react';

OrderSummary

Creates orders that populate analytics data

Overview

Component library architecture

External Dependencies

Recharts

npm install recharts
Full documentation: recharts.org

Framer Motion

npm install framer-motion
Used for stat card hover animations.

Source Reference

View the complete implementation: src/components/dashboard/Analytics.tsx:1-169