Analytics Dashboard
Build a custom analytics dashboard using Pixlpay API data.
Overview
This pattern shows how to:
- Fetch analytics data from the API
- Aggregate and transform data
- Display in a dashboard
Data Collection
Fetch Dashboard Data
javascript
const { PixlpayClient } = require('./pixlpay');
const pixlpay = new PixlpayClient(
process.env.PIXLPAY_STORE_URL,
process.env.PIXLPAY_API_TOKEN
);
async function getDashboardData(startDate, endDate) {
const [revenue, sales, recentOrders] = await Promise.all([
pixlpay.getRevenueAnalytics({
start_date: startDate,
end_date: endDate,
group_by: 'day'
}),
pixlpay.getSalesAnalytics({
start_date: startDate,
end_date: endDate
}),
pixlpay.getOrders({
start_date: startDate,
end_date: endDate,
per_page: 10,
sort_by: 'created_at',
sort_order: 'desc'
})
]);
return {
summary: {
totalRevenue: parseFloat(revenue.data.summary.total_revenue),
totalOrders: revenue.data.summary.total_orders,
averageOrderValue: parseFloat(revenue.data.summary.average_order_value),
},
revenueChart: revenue.data.breakdown.map(d => ({
date: d.period,
revenue: parseFloat(d.total_revenue),
orders: d.order_count
})),
topProducts: sales.data.top_products.slice(0, 5),
recentOrders: recentOrders.data
};
}Compare Periods
javascript
async function getComparison(currentStart, currentEnd, previousStart, previousEnd) {
const [current, previous] = await Promise.all([
pixlpay.getRevenueAnalytics({ start_date: currentStart, end_date: currentEnd }),
pixlpay.getRevenueAnalytics({ start_date: previousStart, end_date: previousEnd })
]);
const currentRevenue = parseFloat(current.data.summary.total_revenue);
const previousRevenue = parseFloat(previous.data.summary.total_revenue);
return {
current: current.data.summary,
previous: previous.data.summary,
revenueChange: ((currentRevenue - previousRevenue) / previousRevenue) * 100,
ordersChange: ((current.data.summary.total_orders - previous.data.summary.total_orders)
/ previous.data.summary.total_orders) * 100
};
}API Endpoint
javascript
const express = require('express');
const app = express();
app.get('/api/dashboard', async (req, res) => {
const { start_date, end_date } = req.query;
try {
const data = await getDashboardData(start_date, end_date);
res.json(data);
} catch (error) {
console.error('Dashboard error:', error);
res.status(500).json({ error: 'Failed to fetch dashboard data' });
}
});
app.get('/api/dashboard/comparison', async (req, res) => {
const today = new Date();
const thirtyDaysAgo = new Date(today - 30 * 24 * 60 * 60 * 1000);
const sixtyDaysAgo = new Date(today - 60 * 24 * 60 * 60 * 1000);
const data = await getComparison(
thirtyDaysAgo.toISOString().split('T')[0],
today.toISOString().split('T')[0],
sixtyDaysAgo.toISOString().split('T')[0],
thirtyDaysAgo.toISOString().split('T')[0]
);
res.json(data);
});
app.listen(3000);React Dashboard Component
jsx
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, Tooltip } from 'recharts';
function Dashboard() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [dateRange, setDateRange] = useState('30d');
useEffect(() => {
fetchDashboard();
}, [dateRange]);
async function fetchDashboard() {
setLoading(true);
const endDate = new Date().toISOString().split('T')[0];
const days = dateRange === '7d' ? 7 : dateRange === '30d' ? 30 : 90;
const startDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
.toISOString().split('T')[0];
const response = await fetch(
`/api/dashboard?start_date=${startDate}&end_date=${endDate}`
);
const result = await response.json();
setData(result);
setLoading(false);
}
if (loading) return <div>Loading...</div>;
return (
<div className="dashboard">
{/* Summary Cards */}
<div className="summary-cards">
<div className="card">
<h3>Revenue</h3>
<p className="value">${data.summary.totalRevenue.toFixed(2)}</p>
</div>
<div className="card">
<h3>Orders</h3>
<p className="value">{data.summary.totalOrders}</p>
</div>
<div className="card">
<h3>Avg Order</h3>
<p className="value">${data.summary.averageOrderValue.toFixed(2)}</p>
</div>
</div>
{/* Revenue Chart */}
<div className="chart-container">
<h3>Revenue Over Time</h3>
<LineChart width={800} height={300} data={data.revenueChart}>
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="revenue" stroke="#10B981" />
</LineChart>
</div>
{/* Top Products */}
<div className="top-products">
<h3>Top Products</h3>
<table>
<thead>
<tr>
<th>Product</th>
<th>Units Sold</th>
<th>Revenue</th>
</tr>
</thead>
<tbody>
{data.topProducts.map(product => (
<tr key={product.id}>
<td>{product.name}</td>
<td>{product.units_sold}</td>
<td>${parseFloat(product.total_revenue).toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Recent Orders */}
<div className="recent-orders">
<h3>Recent Orders</h3>
<table>
<thead>
<tr>
<th>Order</th>
<th>Customer</th>
<th>Total</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{data.recentOrders.map(order => (
<tr key={order.id}>
<td>{order.order_number}</td>
<td>{order.customer_email}</td>
<td>${order.total}</td>
<td>{order.status}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}Caching
Cache API responses to reduce load:
javascript
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5 minute cache
async function getCachedDashboard(startDate, endDate) {
const cacheKey = `dashboard:${startDate}:${endDate}`;
let data = cache.get(cacheKey);
if (!data) {
data = await getDashboardData(startDate, endDate);
cache.set(cacheKey, data);
}
return data;
}Real-Time Updates
Update dashboard when orders come in:
javascript
// Webhook handler
app.post('/webhooks/pixlpay', (req, res) => {
const event = req.body;
if (event.event_type === 'order.paid') {
// Invalidate cache
cache.flushAll();
// Notify connected clients via WebSocket
io.emit('order:new', {
order_number: event.data.order_number,
total: event.data.total
});
}
res.status(200).json({ status: 'received' });
});
// Client-side
socket.on('order:new', (order) => {
// Refresh dashboard data
fetchDashboard();
// Show notification
showToast(`New order: ${order.order_number} - $${order.total}`);
});