> ## Documentation Index
> Fetch the complete documentation index at: https://twigz.arqqin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dashboard

> Comprehensive store analytics and performance metrics

## Overview

The Dashboard API provides comprehensive analytics and key performance indicators for store owners. It includes sales metrics, order analytics, product performance data, and actionable insights to help store owners make informed business decisions.

**Location:** `convex/stores/dashboard.ts`

## Dashboard Overview

Get comprehensive dashboard metrics with key performance indicators.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const overview = await convex.query(api.stores.dashboard.getDashboardOverview);
  ```

  ```javascript JavaScript theme={null}
  const overview = await convex.query("stores.dashboard.getDashboardOverview");
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      f"{CONVEX_URL}/api/query",
      json={
          "path": "stores.dashboard.getDashboardOverview",
          "args": {}
      },
      headers={"Authorization": f"Bearer {token}"}
  )

  overview = response.json()
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "todaysSales": 150.50,
    "activeOrders": 3,
    "ordersToday": 12,
    "totalProducts": 25,
    "averageOrderValue": 45.30
  }
  ```
</ResponseExample>

## Top Selling Products

Retrieve top-selling products based on quantity sold and revenue generated.

<ParamField path="limit" type="number">
  Maximum number of products to return (default: 10)
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const topProducts = await convex.query(api.stores.dashboard.getTopSellingProducts, { 
    limit: 5 
  });
  ```

  ```javascript JavaScript theme={null}
  const topProducts = await convex.query("stores.dashboard.getTopSellingProducts", { 
    limit: 8 
  });
  ```

  ```python Python theme={null}
  response = requests.post(
      f"{CONVEX_URL}/api/query",
      json={
          "path": "stores.dashboard.getTopSellingProducts",
          "args": {"limit": 5}
      },
      headers={"Authorization": f"Bearer {token}"}
  )

  top_products = response.json()
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  [
    {
      "productId": "p123456789",
      "productName": "Pizza Margherita",
      "totalQuantitySold": 25,
      "totalRevenue": 375.00,
      "averagePrice": 15.00,
      "productImage": "https://storage.convex.dev/pizza-margherita.jpg"
    },
    {
      "productId": "p987654321",
      "productName": "Garlic Bread",
      "totalQuantitySold": 18,
      "totalRevenue": 108.00,
      "averagePrice": 6.00,
      "productImage": "https://storage.convex.dev/garlic-bread.jpg"
    },
    {
      "productId": "p555666777",
      "productName": "Caesar Salad",
      "totalQuantitySold": 15,
      "totalRevenue": 180.00,
      "averagePrice": 12.00,
      "productImage": "https://storage.convex.dev/caesar-salad.jpg"
    }
  ]
  ```
</ResponseExample>

## Attention Items

Get items that need immediate store owner attention with priority levels.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const alerts = await convex.query(api.stores.dashboard.getAttentionItems);
  ```

  ```javascript JavaScript theme={null}
  const alerts = await convex.query("stores.dashboard.getAttentionItems");
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  [
    {
      "type": "low_stock",
      "title": "Low Stock Alert",
      "description": "3 products running low on stock",
      "priority": "medium",
      "actionUrl": "/products",
      "count": 3
    },
    {
      "type": "pending_orders",
      "title": "Pending Orders",
      "description": "2 orders waiting for confirmation",
      "priority": "high",
      "actionUrl": "/orders?status=pending",
      "count": 2
    },
    {
      "type": "missing_bank_details",
      "title": "Bank Details Required",
      "description": "Add bank account information to receive payouts",
      "priority": "high",
      "actionUrl": "/settings/bank"
    }
  ]
  ```
</ResponseExample>

## Recent Orders

Get recent orders for quick overview and monitoring.

<ParamField path="limit" type="number">
  Maximum number of orders to return (default: 5)
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const recentOrders = await convex.query(api.stores.dashboard.getRecentOrders, { 
    limit: 10 
  });
  ```

  ```javascript JavaScript theme={null}
  const recentOrders = await convex.query("stores.dashboard.getRecentOrders", { 
    limit: 5 
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  [
    {
      "_id": "o123456789",
      "orderDate": 1640995200000,
      "totalAmount": 45.50,
      "status": "delivered",
      "customerName": "John Doe",
      "itemCount": 3
    },
    {
      "_id": "o987654321",
      "orderDate": 1640994000000,
      "totalAmount": 28.75,
      "status": "preparing",
      "customerName": "Jane Smith",
      "itemCount": 2
    },
    {
      "_id": "o555666777",
      "orderDate": 1640992800000,
      "totalAmount": 62.25,
      "status": "out_for_delivery",
      "customerName": "Mike Johnson",
      "itemCount": 4
    }
  ]
  ```
</ResponseExample>

## Complete Dashboard Component

Here's a comprehensive dashboard implementation:

<CodeGroup>
  ```typescript React Dashboard theme={null}
  import { useQuery } from 'convex/react';
  import { api } from './convex/_generated/api';
  import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';

  function StoreDashboard() {
    const overview = useQuery(api.stores.dashboard.getDashboardOverview);
    const topProducts = useQuery(api.stores.dashboard.getTopSellingProducts, { limit: 5 });
    const attentionItems = useQuery(api.stores.dashboard.getAttentionItems);
    const recentOrders = useQuery(api.stores.dashboard.getRecentOrders, { limit: 8 });

    if (!overview) {
      return <div className="flex items-center justify-center p-8">
        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
        <span className="ml-2">Loading dashboard...</span>
      </div>;
    }

    const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];

    return (
      <div className="p-6 space-y-6">
        {/* Header */}
        <div>
          <h1 className="text-3xl font-bold text-gray-900">Store Dashboard</h1>
          <p className="text-gray-600">Welcome back! Here's what's happening with your store today.</p>
        </div>

        {/* Key Metrics */}
        <div className="grid grid-cols-1 md:grid-cols-5 gap-4">
          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-green-100 rounded-lg">
                <svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Today's Sales</p>
                <p className="text-2xl font-bold text-gray-900">${overview.todaysSales.toFixed(2)}</p>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-blue-100 rounded-lg">
                <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Active Orders</p>
                <p className="text-2xl font-bold text-gray-900">{overview.activeOrders}</p>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-purple-100 rounded-lg">
                <svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Orders Today</p>
                <p className="text-2xl font-bold text-gray-900">{overview.ordersToday}</p>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-yellow-100 rounded-lg">
                <svg className="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Total Products</p>
                <p className="text-2xl font-bold text-gray-900">{overview.totalProducts}</p>
              </div>
            </div>
          </div>

          <div className="bg-white p-6 rounded-lg shadow">
            <div className="flex items-center">
              <div className="p-2 bg-indigo-100 rounded-lg">
                <svg className="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
                </svg>
              </div>
              <div className="ml-4">
                <p className="text-sm font-medium text-gray-600">Avg Order Value</p>
                <p className="text-2xl font-bold text-gray-900">${overview.averageOrderValue.toFixed(2)}</p>
              </div>
            </div>
          </div>
        </div>

        {/* Attention Items */}
        {attentionItems && attentionItems.length > 0 && (
          <div className="bg-white rounded-lg shadow">
            <div className="p-6">
              <h2 className="text-lg font-semibold text-gray-900 mb-4">
                🚨 Needs Your Attention
              </h2>
              <div className="space-y-3">
                {attentionItems.map((item, index) => (
                  <div key={index} className={`p-4 rounded-lg border-l-4 ${
                    item.priority === 'high' ? 'bg-red-50 border-red-400' :
                    item.priority === 'medium' ? 'bg-yellow-50 border-yellow-400' :
                    'bg-blue-50 border-blue-400'
                  }`}>
                    <div className="flex justify-between items-start">
                      <div>
                        <h3 className="font-medium text-gray-900">{item.title}</h3>
                        <p className="text-sm text-gray-600">{item.description}</p>
                      </div>
                      <div className="flex items-center space-x-2">
                        {item.count && (
                          <span className={`px-2 py-1 rounded-full text-xs font-medium ${
                            item.priority === 'high' ? 'bg-red-100 text-red-800' :
                            item.priority === 'medium' ? 'bg-yellow-100 text-yellow-800' :
                            'bg-blue-100 text-blue-800'
                          }`}>
                            {item.count}
                          </span>
                        )}
                        {item.actionUrl && (
                          <button className="text-sm text-blue-600 hover:text-blue-800">
                            Take Action →
                          </button>
                        )}
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}

        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
          {/* Top Products Chart */}
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-semibold text-gray-900 mb-4">Top Selling Products</h2>
            {topProducts && topProducts.length > 0 ? (
              <ResponsiveContainer width="100%" height={300}>
                <BarChart data={topProducts}>
                  <CartesianGrid strokeDasharray="3 3" />
                  <XAxis 
                    dataKey="productName" 
                    angle={-45}
                    textAnchor="end"
                    height={80}
                    interval={0}
                  />
                  <YAxis />
                  <Tooltip />
                  <Bar dataKey="totalQuantitySold" fill="#3B82F6" />
                </BarChart>
              </ResponsiveContainer>
            ) : (
              <div className="flex items-center justify-center h-64 text-gray-500">
                No sales data available yet
              </div>
            )}
          </div>

          {/* Recent Orders */}
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-semibold text-gray-900 mb-4">Recent Orders</h2>
            {recentOrders && recentOrders.length > 0 ? (
              <div className="space-y-3">
                {recentOrders.map((order) => (
                  <div key={order._id} className="flex items-center justify-between p-3 bg-gray-50 rounded">
                    <div>
                      <p className="font-medium text-gray-900">
                        {order.customerName || 'Guest'}
                      </p>
                      <p className="text-sm text-gray-600">
                        {order.itemCount} items • {new Date(order.orderDate).toLocaleDateString()}
                      </p>
                    </div>
                    <div className="text-right">
                      <p className="font-medium text-gray-900">
                        ${order.totalAmount.toFixed(2)}
                      </p>
                      <span className={`px-2 py-1 rounded-full text-xs font-medium ${
                        order.status === 'delivered' ? 'bg-green-100 text-green-800' :
                        order.status === 'preparing' ? 'bg-yellow-100 text-yellow-800' :
                        order.status === 'out_for_delivery' ? 'bg-blue-100 text-blue-800' :
                        'bg-gray-100 text-gray-800'
                      }`}>
                        {order.status.replace('_', ' ')}
                      </span>
                    </div>
                  </div>
                ))}
              </div>
            ) : (
              <div className="flex items-center justify-center h-64 text-gray-500">
                No recent orders
              </div>
            )}
          </div>
        </div>

        {/* Revenue Chart */}
        {topProducts && topProducts.length > 0 && (
          <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-semibold text-gray-900 mb-4">Revenue by Product</h2>
            <ResponsiveContainer width="100%" height={300}>
              <PieChart>
                <Pie
                  data={topProducts}
                  cx="50%"
                  cy="50%"
                  labelLine={false}
                  label={({ productName, totalRevenue }) => `${productName}: $${totalRevenue}`}
                  outerRadius={80}
                  fill="#8884d8"
                  dataKey="totalRevenue"
                >
                  {topProducts.map((entry, index) => (
                    <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
                  ))}
                </Pie>
                <Tooltip />
              </PieChart>
            </ResponsiveContainer>
          </div>
        )}
      </div>
    );
  }

  export default StoreDashboard;
  ```
</CodeGroup>

## Dashboard Metrics

<CardGroup cols={2}>
  <Card title="Sales Analytics" icon="chart-line">
    Track daily sales, revenue trends, and average order values
  </Card>

  <Card title="Order Management" icon="shopping-cart">
    Monitor active orders, order status, and fulfillment metrics
  </Card>

  <Card title="Product Performance" icon="star">
    Identify top-selling products and inventory insights
  </Card>

  <Card title="Business Intelligence" icon="brain">
    Get actionable insights and attention items for business growth
  </Card>
</CardGroup>

## Attention Item Types

The dashboard identifies various issues that need store owner attention:

<AccordionGroup>
  <Accordion title="Store Verification">
    **Type**: `store_verification`

    Store application pending approval or additional documents required.

    ```json theme={null}
    {
      "type": "store_verification",
      "title": "Store Verification Pending",
      "description": "Your store is under review",
      "priority": "high"
    }
    ```
  </Accordion>

  <Accordion title="Low Stock">
    **Type**: `low_stock`

    Products running low on inventory that need restocking.

    ```json theme={null}
    {
      "type": "low_stock", 
      "title": "Low Stock Alert",
      "description": "3 products running low on stock",
      "priority": "medium",
      "count": 3,
      "actionUrl": "/products"
    }
    ```
  </Accordion>

  <Accordion title="Missing Bank Details">
    **Type**: `missing_bank_details`

    Bank account information required for payout processing.

    ```json theme={null}
    {
      "type": "missing_bank_details",
      "title": "Bank Details Required", 
      "description": "Add bank account to receive payouts",
      "priority": "high",
      "actionUrl": "/settings/bank"
    }
    ```
  </Accordion>

  <Accordion title="Low Wallet Balance">
    **Type**: `low_wallet_balance`

    Store wallet balance is low, may affect operations.

    ```json theme={null}
    {
      "type": "low_wallet_balance",
      "title": "Low Wallet Balance",
      "description": "Wallet balance below minimum threshold", 
      "priority": "medium",
      "actionUrl": "/wallet"
    }
    ```
  </Accordion>

  <Accordion title="Pending Orders">
    **Type**: `pending_orders`

    Orders waiting for confirmation or processing.

    ```json theme={null}
    {
      "type": "pending_orders",
      "title": "Pending Orders",
      "description": "2 orders waiting for confirmation",
      "priority": "high",
      "count": 2,
      "actionUrl": "/orders?status=pending"
    }
    ```
  </Accordion>
</AccordionGroup>

## Real-time Dashboard Updates

Dashboard data updates automatically as business activities occur:

```typescript theme={null}
// Dashboard automatically reflects:
// - New orders placed
// - Order status changes
// - Product sales updates
// - Inventory changes
// - Payment processing
// - Wallet balance changes

function DashboardWithRealTimeUpdates() {
  const overview = useQuery(api.stores.dashboard.getDashboardOverview);
  
  // Data refreshes automatically when:
  // - Customer places new order → ordersToday increases
  // - Order is delivered → activeOrders decreases, todaysSales increases
  // - Product inventory changes → attention items may update
  // - Bank details added → attention items update
  
  return <StoreDashboard overview={overview} />;
}
```

## Error Handling

<AccordionGroup>
  <Accordion title="Store not found">
    **Status Code**: `404`

    ```json theme={null}
    {
      "error": "Store not found for authenticated user"
    }
    ```
  </Accordion>

  <Accordion title="Insufficient permissions">
    **Status Code**: `403`

    ```json theme={null}
    {
      "error": "Access denied. User is not the store owner."
    }
    ```
  </Accordion>

  <Accordion title="Store not approved">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Dashboard not available for unapproved stores"
    }
    ```
  </Accordion>
</AccordionGroup>

<Note>
  Dashboard metrics are calculated in real-time and update automatically as business activities occur. Historical data is maintained for trend analysis.
</Note>

<Tip>
  Use the attention items to proactively address business issues before they impact operations. High-priority items should be addressed immediately.
</Tip>
