
Creating Dashboards
- 315 installs
- 426 repo stars
- Updated December 11, 2025
- ancoleman/ai-design-components
Creating-dashboards is a Claude Code skill that creates dashboard and analytics interfaces combining data visualization, KPI cards, real-time updates, and interactive layouts.
About
Creating-dashboards is a Claude Code skill for building dashboard and analytics interfaces that combine data visualization, KPI cards, real-time updates, and interactive layouts. A developer uses it when building business-intelligence dashboards, monitoring systems, or executive reports. It covers KPI card anatomy, widget containers, cross-widget filter coordination, real-time updates via server-sent events, and responsive layouts using Tremor or react-grid-layout.
- KPI cards, charts, tables, and filters in coordinated layouts
- Cross-widget filter context and real-time updates via SSE
- Tremor and react-grid-layout for quick or customizable dashboards
Creating Dashboards by the numbers
- 315 all-time installs (skills.sh)
- Ranked #740 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
creating-dashboards capabilities & compatibility
- Capabilities
- creating dashboards · building tables · building forms · building ai chat
- Use cases
- frontend · ui design · data analysis
What creating-dashboards says it does
Creates comprehensive dashboard and analytics interfaces that combine data visualization, KPI cards, real-time updates, and interactive layouts.
Server-Sent Events (Recommended for Dashboards)
npx skills add https://github.com/ancoleman/ai-design-components --skill creating-dashboardsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 315 |
|---|---|
| repo stars | ★ 426 |
| Last updated | December 11, 2025 |
| Repository | ancoleman/ai-design-components ↗ |
What it does
Build an analytics dashboard with KPI cards, coordinated filters, and real-time widget updates.
Who is it for?
Building BI/analytics dashboards with coordinated KPI cards, charts, and filters.
Skip if: Backend data pipelines or metric computation.
When should I use this skill?
Building a business-intelligence dashboard, monitoring system, or KPI display.
What you get
A responsive dashboard with coordinated filters, real-time updates, and reusable widgets.
- KPI cards and widget containers
- Cross-widget filter context
- Real-time update wiring
By the numbers
- 3 dashboard layout types (Fixed, Customizable Grid, Template-Based)
- 3 responsive breakpoints (desktop, tablet, mobile)
Files
Creating Dashboards
Purpose
This skill enables the creation of sophisticated dashboard interfaces that aggregate and present data through coordinated widgets including KPI cards, charts, tables, and filters. Dashboards serve as centralized command centers for data-driven decision making, combining multiple component types from other skills (data-viz, tables, design-tokens) into unified analytics experiences with real-time updates, responsive layouts, and interactive filtering.
When to Use
Activate this skill when:
- Building business intelligence or analytics dashboards
- Creating executive reporting interfaces
- Implementing real-time monitoring systems
- Designing KPI displays with metrics and trends
- Developing customizable widget-based layouts
- Coordinating filters across multiple data displays
- Building responsive data-heavy interfaces
- Implementing drag-and-drop dashboard editors
- Creating template-based analytics systems
- Designing multi-tenant SaaS dashboards
Core Dashboard Elements
KPI Card Anatomy
┌────────────────────────────┐
│ Revenue (This Month) │ ← Label with time period
│ │
│ $1,245,832 │ ← Big number (primary metric)
│ ↑ 15.3% vs last month │ ← Trend indicator with comparison
│ ▂▃▅▆▇█ (sparkline) │ ← Mini visualization
└────────────────────────────┘Widget Container Structure
- Title bar with widget name and actions
- Loading state (skeleton or spinner)
- Error boundary with retry option
- Resize handles for adjustable layouts
- Settings menu (export, configure, refresh)
Dashboard Layout Types
Fixed Layout: Designer-defined placement, consistent across users Customizable Grid: User drag-and-drop, resizable widgets, saved layouts Template-Based: Pre-built patterns, industry-specific starting points
Global Dashboard Controls
- Date range picker (affects all widgets)
- Filter panel (coordinated across widgets)
- Refresh controls (manual/auto-refresh)
- Export actions (PDF, image, data)
- Theme switcher (light/dark/custom)
Implementation Approach
1. Choose Dashboard Architecture
For Quick Analytics Dashboard → Use Tremor Pre-built KPI cards, charts, and tables with minimal code:
npm install @tremor/reactFor Customizable Dashboard → Use react-grid-layout Drag-and-drop, resizable widgets, user-defined layouts:
npm install react-grid-layout2. Set Up Global State Management
Implement filter context for cross-widget coordination:
// Dashboard context for shared filters
const DashboardContext = createContext({
filters: { dateRange: null, categories: [] },
setFilters: () => {},
refreshInterval: 30000
});
// Wrap dashboard with provider
<DashboardContext.Provider value={dashboardState}>
<FilterPanel />
<WidgetGrid />
</DashboardContext.Provider>3. Implement Data Fetching Strategy
Parallel Loading: Fetch all widget data simultaneously Lazy Loading: Load visible widgets first, others on scroll Cached Updates: Serve from cache while fetching fresh data
4. Configure Real-Time Updates
Server-Sent Events (Recommended for Dashboards):
const eventSource = new EventSource('/api/dashboard/stream');
eventSource.onmessage = (event) => {
const update = JSON.parse(event.data);
updateWidget(update.widgetId, update.data);
};5. Apply Responsive Design
Define breakpoints for different screen sizes:
- Desktop (>1200px): Multi-column grid
- Tablet (768-1200px): 2-column layout
- Mobile (<768px): Single column stack
Quick Start with Tremor
Basic KPI Dashboard
import { Card, Grid, Metric, Text, BadgeDelta, AreaChart } from '@tremor/react';
function QuickDashboard({ data }) {
return (
<Grid numItems={1} numItemsSm={2} numItemsLg={4} className="gap-4">
{/* KPI Cards */}
<Card>
<Text>Total Revenue</Text>
<Metric>$45,231.89</Metric>
<BadgeDelta deltaType="increase">+12.5%</BadgeDelta>
</Card>
<Card>
<Text>Active Users</Text>
<Metric>1,234</Metric>
<BadgeDelta deltaType="decrease">-2.3%</BadgeDelta>
</Card>
{/* Chart Widget */}
<Card className="lg:col-span-2">
<Text>Revenue Trend</Text>
<AreaChart
data={data.revenue}
index="date"
categories={["revenue"]}
valueFormatter={(value) => `$${value.toLocaleString()}`}
/>
</Card>
</Grid>
);
}For complete implementation, see examples/tremor-dashboard.tsx.
Customizable Dashboard Implementation
Drag-and-Drop Grid Layout
import { Responsive, WidthProvider } from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
const ResponsiveGridLayout = WidthProvider(Responsive);
function CustomizableDashboard() {
const [layouts, setLayouts] = useState(getStoredLayouts());
return (
<ResponsiveGridLayout
layouts={layouts}
breakpoints={{ lg: 1200, md: 996, sm: 768 }}
cols={{ lg: 12, md: 10, sm: 6 }}
rowHeight={60}
onLayoutChange={(layout, layouts) => {
setLayouts(layouts);
localStorage.setItem('dashboardLayout', JSON.stringify(layouts));
}}
draggableHandle=".widget-header"
>
<div key="kpi1">
<KPIWidget data={kpiData} />
</div>
<div key="chart1">
<ChartWidget data={chartData} />
</div>
<div key="table1">
<TableWidget data={tableData} />
</div>
</ResponsiveGridLayout>
);
}For full example with widget catalog, see examples/customizable-dashboard.tsx.
Real-Time Data Patterns
Server-Sent Events (Recommended)
Best for unidirectional updates from server to dashboard:
function useSSEUpdates(endpoint) {
useEffect(() => {
const eventSource = new EventSource(endpoint);
eventSource.onmessage = (event) => {
const update = JSON.parse(event.data);
// Update specific widget or all widgets
dispatch({ type: 'UPDATE_WIDGET', payload: update });
};
return () => eventSource.close();
}, [endpoint]);
}WebSocket (For Bidirectional)
Use when dashboard needs to send commands back to server:
const ws = new WebSocket('ws://localhost:3000/dashboard');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
updateDashboard(data);
};
// Send filter changes to server
ws.send(JSON.stringify({ type: 'FILTER_CHANGE', filters }));Smart Polling Fallback
For environments without WebSocket/SSE support:
function useSmartPolling(fetchData, interval = 30000) {
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
if (isPaused || document.hidden) return;
const timer = setInterval(fetchData, interval);
return () => clearInterval(timer);
}, [isPaused, interval]);
// Pause when tab inactive
useEffect(() => {
const handleVisibilityChange = () => {
setIsPaused(document.hidden);
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
}, []);
}For detailed patterns including error handling and reconnection, see references/real-time-updates.md.
Performance Optimization
Lazy Loading Strategy
function DashboardGrid({ widgets }) {
const [visibleWidgets, setVisibleWidgets] = useState(new Set());
return widgets.map(widget => (
<LazyLoad
key={widget.id}
height={widget.height}
offset={100}
once
placeholder={<WidgetSkeleton />}
>
<Widget {...widget} />
</LazyLoad>
));
}Parallel Data Fetching
// Fetch all widget data simultaneously
const loadDashboard = async () => {
const [kpis, charts, tables] = await Promise.all([
fetchKPIs(),
fetchChartData(),
fetchTableData()
]);
return { kpis, charts, tables };
};Widget-Level Caching
function CachedWidget({ id, fetcher, ttl = 60000 }) {
const cache = useRef({ data: null, timestamp: 0 });
const getData = async () => {
const now = Date.now();
if (cache.current.data && now - cache.current.timestamp < ttl) {
return cache.current.data;
}
const fresh = await fetcher();
cache.current = { data: fresh, timestamp: now };
return fresh;
};
// Use cached data while fetching fresh
return <Widget data={cache.current.data} onRefresh={getData} />;
}To analyze and optimize dashboard performance, run:
python scripts/optimize-dashboard-performance.py --analyze dashboard-config.jsonCross-Skill Integration
Using Data Visualization Components
Reference the data-viz skill for chart widgets:
// Use charts from data-viz skill
import { createChart } from '../data-viz/chart-factory';
const revenueChart = createChart('area', {
data: revenueData,
xAxis: 'date',
yAxis: 'revenue',
theme: dashboardTheme
});Integrating Data Tables
Reference the tables skill for data grids:
// Use advanced tables from tables skill
import { DataGrid } from '../tables/data-grid';
<DataGrid
data={transactions}
columns={columnDefs}
pagination={true}
sorting={true}
filtering={true}
/>Applying Design Tokens
Use the design-tokens skill for consistent theming:
// Dashboard-specific tokens from design-tokens skill
const dashboardTokens = {
'--dashboard-bg': 'var(--color-bg-secondary)',
'--widget-bg': 'var(--color-white)',
'--widget-shadow': 'var(--shadow-lg)',
'--kpi-value-size': 'var(--font-size-4xl)',
'--kpi-trend-positive': 'var(--color-success)',
'--kpi-trend-negative': 'var(--color-error)'
};Filter Input Components
Optionally use the forms skill for filter controls:
// Advanced filter inputs from forms skill
import { DateRangePicker, MultiSelect } from '../forms/inputs';
<FilterPanel>
<DateRangePicker onChange={handleDateChange} />
<MultiSelect options={categories} onChange={handleCategoryFilter} />
</FilterPanel>Library Selection Guide
Choose Tremor When:
- Need to build dashboards quickly
- Want pre-styled, professional components
- Using Tailwind CSS in your project
- Building standard analytics interfaces
- Limited customization requirements
Choose react-grid-layout When:
- Users need to customize layouts
- Drag-and-drop is required
- Different users need different views
- Building a dashboard builder tool
- Maximum flexibility is priority
Combine Both When:
- Use Tremor for widget contents (KPIs, charts)
- Use react-grid-layout for layout management
- Get best of both worlds
Bundled Resources
Scripts (Token-Free Execution)
scripts/generate-dashboard-layout.py- Generate responsive grid configurationsscripts/calculate-kpi-metrics.py- Calculate trends, comparisons, sparklinesscripts/validate-widget-config.py- Validate widget and filter configurationsscripts/optimize-dashboard-performance.py- Analyze and optimize performancescripts/export-dashboard.py- Export dashboards to various formats
Run scripts directly without loading into context:
python scripts/calculate-kpi-metrics.py --data metrics.json --period monthlyReferences (Detailed Patterns)
references/kpi-card-patterns.md- KPI card design patterns and variationsreferences/layout-strategies.md- Grid systems and responsive approachesreferences/real-time-updates.md- WebSocket, SSE, and polling implementationsreferences/filter-coordination.md- Cross-widget filter synchronizationreferences/performance-optimization.md- Advanced optimization techniquesreferences/library-guide.md- Detailed Tremor and react-grid-layout guides
Examples (Complete Implementations)
examples/sales-dashboard.tsx- Full sales analytics dashboardexamples/monitoring-dashboard.tsx- Real-time monitoring with alertsexamples/executive-dashboard.tsx- Polished executive reportingexamples/customizable-dashboard.tsx- Drag-and-drop with persistenceexamples/tremor-dashboard.tsx- Quick Tremor implementationexamples/filter-context.tsx- Global filter coordination
Assets (Templates & Configurations)
assets/dashboard-templates.json- Pre-built dashboard layoutsassets/widget-library.json- Widget catalog and configurationsassets/grid-layouts.json- Responsive grid configurationsassets/kpi-formats.json- Number formatting rulesassets/theme-tokens.json- Dashboard-specific design tokens
Dashboard Creation Workflow
1. Define Requirements: Fixed or customizable? Real-time or static? 2. Choose Libraries: Tremor for quick, react-grid-layout for flexible 3. Set Up Structure: Global state, filter context, layout system 4. Build Widgets: KPI cards, charts (data-viz), tables (tables skill) 5. Implement Data Flow: Fetching strategy, caching, updates 6. Add Interactivity: Filters, drill-downs, exports 7. Optimize Performance: Lazy loading, parallel fetching, caching 8. Apply Theming: Use design-tokens for consistent styling 9. Test Responsiveness: Desktop, tablet, mobile breakpoints 10. Deploy & Monitor: Track performance, user engagement
For specific patterns and detailed implementations, explore the bundled resources referenced above.
{
"templates": [
{
"id": "sales-analytics",
"name": "Sales Analytics Dashboard",
"description": "Comprehensive sales tracking with revenue, orders, and customer metrics",
"category": "Sales",
"thumbnail": "/templates/sales-analytics.png",
"widgets": [
{
"id": "revenue-kpi",
"type": "kpi",
"title": "Total Revenue",
"metric": "revenue",
"data_source": "sales_metrics",
"format": "currency",
"aggregation": "sum",
"trend": {
"enabled": true,
"comparison": "previous_period"
},
"position": { "x": 0, "y": 0, "w": 3, "h": 2 }
},
{
"id": "orders-kpi",
"type": "kpi",
"title": "Total Orders",
"metric": "order_count",
"data_source": "sales_metrics",
"format": "number",
"aggregation": "count",
"trend": {
"enabled": true,
"comparison": "previous_period"
},
"position": { "x": 3, "y": 0, "w": 3, "h": 2 }
},
{
"id": "conversion-kpi",
"type": "kpi",
"title": "Conversion Rate",
"metric": "conversion_rate",
"data_source": "sales_metrics",
"format": "percentage",
"aggregation": "average",
"position": { "x": 6, "y": 0, "w": 3, "h": 2 }
},
{
"id": "customers-kpi",
"type": "kpi",
"title": "New Customers",
"metric": "new_customers",
"data_source": "customer_metrics",
"format": "number",
"aggregation": "count",
"position": { "x": 9, "y": 0, "w": 3, "h": 2 }
},
{
"id": "revenue-chart",
"type": "chart",
"title": "Revenue Trend",
"chart_type": "area",
"data_source": "revenue_history",
"axes": {
"x": "date",
"y": "revenue"
},
"position": { "x": 0, "y": 2, "w": 8, "h": 4 }
},
{
"id": "top-products",
"type": "table",
"title": "Top Products",
"data_source": "product_performance",
"columns": [
{ "field": "product_name", "label": "Product" },
{ "field": "revenue", "label": "Revenue", "format": "currency" },
{ "field": "units", "label": "Units Sold", "format": "number" },
{ "field": "growth", "label": "Growth", "format": "percentage" }
],
"position": { "x": 8, "y": 2, "w": 4, "h": 4 }
}
],
"filters": [
{
"id": "date-range",
"type": "date_range",
"label": "Date Range",
"default": "last_30_days"
},
{
"id": "region-filter",
"type": "multiselect",
"label": "Regions",
"options": ["North", "South", "East", "West"],
"default": []
}
],
"refresh_interval": 300000
},
{
"id": "marketing-analytics",
"name": "Marketing Analytics Dashboard",
"description": "Track marketing campaigns, leads, and conversion metrics",
"category": "Marketing",
"thumbnail": "/templates/marketing-analytics.png",
"widgets": [
{
"id": "leads-kpi",
"type": "kpi",
"title": "Total Leads",
"metric": "lead_count",
"data_source": "marketing_metrics",
"format": "number",
"aggregation": "count",
"position": { "x": 0, "y": 0, "w": 3, "h": 2 }
},
{
"id": "conversion-funnel",
"type": "chart",
"title": "Conversion Funnel",
"chart_type": "funnel",
"data_source": "funnel_data",
"position": { "x": 3, "y": 0, "w": 6, "h": 4 }
},
{
"id": "campaign-performance",
"type": "table",
"title": "Campaign Performance",
"data_source": "campaigns",
"columns": [
{ "field": "campaign_name", "label": "Campaign" },
{ "field": "impressions", "label": "Impressions", "format": "number" },
{ "field": "clicks", "label": "Clicks", "format": "number" },
{ "field": "ctr", "label": "CTR", "format": "percentage" },
{ "field": "cost", "label": "Cost", "format": "currency" }
],
"position": { "x": 0, "y": 4, "w": 12, "h": 4 }
}
],
"filters": [
{
"id": "campaign-type",
"type": "select",
"label": "Campaign Type",
"options": ["Email", "Social", "PPC", "Display"],
"default": "all"
}
],
"refresh_interval": 600000
},
{
"id": "operations-monitoring",
"name": "Operations Monitoring Dashboard",
"description": "Real-time monitoring of system health and performance",
"category": "Operations",
"thumbnail": "/templates/operations-monitoring.png",
"widgets": [
{
"id": "system-status",
"type": "custom",
"title": "System Status",
"component": "SystemStatusGrid",
"data_source": "system_health",
"position": { "x": 0, "y": 0, "w": 12, "h": 2 }
},
{
"id": "cpu-usage",
"type": "chart",
"title": "CPU Usage",
"chart_type": "line",
"data_source": "metrics/cpu",
"axes": {
"x": "timestamp",
"y": "usage_percent"
},
"position": { "x": 0, "y": 2, "w": 4, "h": 3 }
},
{
"id": "memory-usage",
"type": "chart",
"title": "Memory Usage",
"chart_type": "line",
"data_source": "metrics/memory",
"axes": {
"x": "timestamp",
"y": "usage_percent"
},
"position": { "x": 4, "y": 2, "w": 4, "h": 3 }
},
{
"id": "network-traffic",
"type": "chart",
"title": "Network Traffic",
"chart_type": "area",
"data_source": "metrics/network",
"axes": {
"x": "timestamp",
"y": ["inbound", "outbound"]
},
"position": { "x": 8, "y": 2, "w": 4, "h": 3 }
},
{
"id": "alerts-timeline",
"type": "timeline",
"title": "Recent Alerts",
"data_source": "alerts",
"position": { "x": 0, "y": 5, "w": 12, "h": 3 }
}
],
"filters": [
{
"id": "time-window",
"type": "select",
"label": "Time Window",
"options": ["1h", "6h", "24h", "7d"],
"default": "1h"
},
{
"id": "severity",
"type": "multiselect",
"label": "Alert Severity",
"options": ["Critical", "Warning", "Info"],
"default": ["Critical", "Warning"]
}
],
"refresh_interval": 5000
},
{
"id": "financial-overview",
"name": "Financial Overview Dashboard",
"description": "Financial metrics, P&L, and budget tracking",
"category": "Finance",
"thumbnail": "/templates/financial-overview.png",
"widgets": [
{
"id": "revenue-ytd",
"type": "kpi",
"title": "Revenue YTD",
"metric": "revenue_ytd",
"data_source": "financial_metrics",
"format": "currency",
"position": { "x": 0, "y": 0, "w": 3, "h": 2 }
},
{
"id": "expenses-ytd",
"type": "kpi",
"title": "Expenses YTD",
"metric": "expenses_ytd",
"data_source": "financial_metrics",
"format": "currency",
"position": { "x": 3, "y": 0, "w": 3, "h": 2 }
},
{
"id": "profit-margin",
"type": "kpi",
"title": "Profit Margin",
"metric": "profit_margin",
"data_source": "financial_metrics",
"format": "percentage",
"position": { "x": 6, "y": 0, "w": 3, "h": 2 }
},
{
"id": "cash-flow",
"type": "kpi",
"title": "Cash Flow",
"metric": "cash_flow",
"data_source": "financial_metrics",
"format": "currency",
"trend": {
"enabled": true,
"comparison": "previous_month"
},
"position": { "x": 9, "y": 0, "w": 3, "h": 2 }
},
{
"id": "pl-chart",
"type": "chart",
"title": "P&L Trend",
"chart_type": "bar",
"data_source": "pl_monthly",
"axes": {
"x": "month",
"y": ["revenue", "expenses", "profit"]
},
"position": { "x": 0, "y": 2, "w": 8, "h": 4 }
},
{
"id": "budget-vs-actual",
"type": "chart",
"title": "Budget vs Actual",
"chart_type": "grouped-bar",
"data_source": "budget_comparison",
"axes": {
"x": "category",
"y": ["budget", "actual"]
},
"position": { "x": 8, "y": 2, "w": 4, "h": 4 }
}
],
"filters": [
{
"id": "fiscal-period",
"type": "select",
"label": "Fiscal Period",
"options": ["Q1", "Q2", "Q3", "Q4", "YTD"],
"default": "YTD"
},
{
"id": "department",
"type": "multiselect",
"label": "Department",
"options": ["Sales", "Marketing", "Operations", "R&D", "Admin"],
"default": []
}
],
"refresh_interval": 3600000
},
{
"id": "executive-summary",
"name": "Executive Summary Dashboard",
"description": "High-level KPIs and trends for executive team",
"category": "Executive",
"thumbnail": "/templates/executive-summary.png",
"widgets": [
{
"id": "company-health-score",
"type": "gauge",
"title": "Company Health Score",
"metric": "health_score",
"data_source": "executive_metrics",
"position": { "x": 4, "y": 0, "w": 4, "h": 3 }
},
{
"id": "key-metrics-grid",
"type": "custom",
"title": "Key Performance Indicators",
"component": "ExecutiveKPIGrid",
"data_source": "executive_kpis",
"position": { "x": 0, "y": 3, "w": 12, "h": 2 }
},
{
"id": "department-performance",
"type": "chart",
"title": "Department Performance",
"chart_type": "radar",
"data_source": "department_scores",
"position": { "x": 0, "y": 5, "w": 6, "h": 4 }
},
{
"id": "strategic-initiatives",
"type": "table",
"title": "Strategic Initiatives",
"data_source": "initiatives",
"columns": [
{ "field": "initiative", "label": "Initiative" },
{ "field": "owner", "label": "Owner" },
{ "field": "progress", "label": "Progress", "type": "progress" },
{ "field": "status", "label": "Status", "type": "badge" }
],
"position": { "x": 6, "y": 5, "w": 6, "h": 4 }
}
],
"filters": [],
"refresh_interval": 1800000
}
],
"metadata": {
"version": "1.0.0",
"created": "2025-01-01",
"author": "Dashboard Templates System"
}
}{
"layouts": {
"executive": {
"name": "Executive Dashboard",
"description": "High-level KPIs for executives and leadership",
"grid": {
"columns": 12,
"rowHeight": 100,
"breakpoints": { "lg": 1200, "md": 996, "sm": 768, "xs": 480 }
},
"widgets": [
{
"id": "revenue-kpi",
"type": "kpi-card",
"title": "Total Revenue",
"position": { "x": 0, "y": 0, "w": 3, "h": 1 },
"dataSource": "revenue_total",
"format": "currency"
},
{
"id": "users-kpi",
"type": "kpi-card",
"title": "Active Users",
"position": { "x": 3, "y": 0, "w": 3, "h": 1 },
"dataSource": "users_active",
"format": "number"
},
{
"id": "conversion-kpi",
"type": "kpi-card",
"title": "Conversion Rate",
"position": { "x": 6, "y": 0, "w": 3, "h": 1 },
"dataSource": "conversion_rate",
"format": "percentage"
},
{
"id": "growth-kpi",
"type": "kpi-card",
"title": "Growth Rate",
"position": { "x": 9, "y": 0, "w": 3, "h": 1 },
"dataSource": "growth_rate",
"format": "percentage"
},
{
"id": "revenue-chart",
"type": "line-chart",
"title": "Revenue Trend (6 Months)",
"position": { "x": 0, "y": 1, "w": 8, "h": 2 },
"dataSource": "revenue_timeseries",
"xAxis": "date",
"yAxis": "amount"
},
{
"id": "top-products",
"type": "bar-chart",
"title": "Top Products",
"position": { "x": 8, "y": 1, "w": 4, "h": 2 },
"dataSource": "products_ranking",
"xAxis": "product",
"yAxis": "sales"
}
]
},
"analytics": {
"name": "Analytics Dashboard",
"description": "Detailed analytics with multiple chart types",
"grid": {
"columns": 12,
"rowHeight": 80
},
"widgets": [
{
"id": "traffic-sources",
"type": "pie-chart",
"title": "Traffic Sources",
"position": { "x": 0, "y": 0, "w": 4, "h": 2 },
"dataSource": "traffic_sources"
},
{
"id": "user-activity",
"type": "heatmap",
"title": "User Activity by Hour",
"position": { "x": 4, "y": 0, "w": 8, "h": 2 },
"dataSource": "hourly_activity"
},
{
"id": "funnel",
"type": "funnel-chart",
"title": "Conversion Funnel",
"position": { "x": 0, "y": 2, "w": 6, "h": 2 },
"dataSource": "conversion_funnel"
},
{
"id": "geographic",
"type": "map",
"title": "Users by Geography",
"position": { "x": 6, "y": 2, "w": 6, "h": 2 },
"dataSource": "geographic_distribution"
}
]
},
"monitoring": {
"name": "System Monitoring",
"description": "Real-time infrastructure and application monitoring",
"grid": {
"columns": 12,
"rowHeight": 100
},
"widgets": [
{
"id": "cpu-gauge",
"type": "gauge",
"title": "CPU Usage",
"position": { "x": 0, "y": 0, "w": 3, "h": 1 },
"dataSource": "system_cpu_percent",
"min": 0,
"max": 100,
"thresholds": [
{ "value": 70, "color": "warning" },
{ "value": 90, "color": "danger" }
]
},
{
"id": "memory-gauge",
"type": "gauge",
"title": "Memory Usage",
"position": { "x": 3, "y": 0, "w": 3, "h": 1 },
"dataSource": "system_memory_percent",
"min": 0,
"max": 100,
"thresholds": [
{ "value": 80, "color": "warning" },
{ "value": 95, "color": "danger" }
]
},
{
"id": "request-rate",
"type": "line-chart",
"title": "Request Rate (5min)",
"position": { "x": 0, "y": 1, "w": 6, "h": 2 },
"dataSource": "http_requests_per_second",
"realtime": true,
"refreshInterval": 5000
},
{
"id": "error-log",
"type": "log-stream",
"title": "Recent Errors",
"position": { "x": 6, "y": 1, "w": 6, "h": 2 },
"dataSource": "error_logs",
"maxLines": 50,
"realtime": true
}
]
},
"customizable": {
"name": "Customizable Grid",
"description": "User-customizable dashboard with drag-drop",
"grid": {
"columns": 12,
"rowHeight": 100,
"isDraggable": true,
"isResizable": true
},
"widgets": [
{
"id": "widget-1",
"type": "kpi-card",
"title": "Metric 1",
"position": { "x": 0, "y": 0, "w": 3, "h": 1 },
"dataSource": "metric_1"
}
]
}
},
"widgetTypes": [
"kpi-card",
"line-chart",
"bar-chart",
"pie-chart",
"area-chart",
"gauge",
"table",
"heatmap",
"funnel-chart",
"map",
"log-stream",
"calendar",
"timeline"
]
}
{
"formatters": {
"currency": {
"type": "number",
"prefix": "$",
"decimals": 2,
"thousandsSeparator": ",",
"examples": ["$1,234.56", "$10,000.00"]
},
"percentage": {
"type": "number",
"suffix": "%",
"decimals": 1,
"examples": ["45.2%", "99.9%"]
},
"number": {
"type": "number",
"decimals": 0,
"thousandsSeparator": ",",
"examples": ["1,234", "1,000,000"]
},
"compact": {
"type": "number",
"compact": true,
"examples": ["1.2K", "5.4M", "2.1B"]
},
"duration": {
"type": "duration",
"units": "auto",
"examples": ["2h 30m", "45s", "1d 3h"]
},
"bytes": {
"type": "bytes",
"units": "auto",
"examples": ["1.2 MB", "5.4 GB", "128 KB"]
},
"rate": {
"type": "rate",
"suffix": "/s",
"decimals": 1,
"examples": ["123.4/s", "5.2K/s"]
}
},
"trendIndicators": {
"increase": {
"positive": {
"icon": "↑",
"color": "#10b981",
"label": "up"
},
"negative": {
"icon": "↑",
"color": "#ef4444",
"label": "up"
}
},
"decrease": {
"positive": {
"icon": "↓",
"color": "#10b981",
"label": "down"
},
"negative": {
"icon": "↓",
"color": "#ef4444",
"label": "down"
}
},
"neutral": {
"icon": "→",
"color": "#6b7280",
"label": "unchanged"
}
},
"kpiTemplates": {
"revenue": {
"title": "Total Revenue",
"format": "currency",
"trend": "increase",
"trendType": "positive",
"comparisonPeriod": "previous_period",
"icon": "dollar-sign"
},
"users": {
"title": "Active Users",
"format": "compact",
"trend": "increase",
"trendType": "positive",
"comparisonPeriod": "previous_month",
"icon": "users"
},
"conversion": {
"title": "Conversion Rate",
"format": "percentage",
"trend": "increase",
"trendType": "positive",
"comparisonPeriod": "previous_week",
"icon": "trending-up"
},
"errorRate": {
"title": "Error Rate",
"format": "percentage",
"trend": "decrease",
"trendType": "positive",
"comparisonPeriod": "previous_day",
"icon": "alert-triangle",
"thresholds": [
{ "max": 1, "color": "success" },
{ "min": 1, "max": 5, "color": "warning" },
{ "min": 5, "color": "danger" }
]
},
"latency": {
"title": "P95 Latency",
"format": "duration",
"trend": "decrease",
"trendType": "positive",
"comparisonPeriod": "previous_hour",
"icon": "clock",
"thresholds": [
{ "max": 100, "color": "success" },
{ "min": 100, "max": 500, "color": "warning" },
{ "min": 500, "color": "danger" }
]
},
"throughput": {
"title": "Throughput",
"format": "rate",
"trend": "increase",
"trendType": "positive",
"comparisonPeriod": "previous_5min",
"icon": "activity"
}
},
"comparisonPeriods": [
{ "id": "previous_hour", "label": "vs. Previous Hour", "offset": 3600 },
{ "id": "previous_day", "label": "vs. Yesterday", "offset": 86400 },
{ "id": "previous_week", "label": "vs. Last Week", "offset": 604800 },
{ "id": "previous_month", "label": "vs. Last Month", "offset": 2592000 },
{ "id": "previous_period", "label": "vs. Previous Period", "offset": "dynamic" }
]
}
{
"themes": {
"light": {
"colors": {
"background": {
"primary": "#ffffff",
"secondary": "#f8fafc",
"tertiary": "#f1f5f9"
},
"text": {
"primary": "#0f172a",
"secondary": "#475569",
"tertiary": "#94a3b8"
},
"border": {
"default": "#e2e8f0",
"subtle": "#f1f5f9"
},
"status": {
"success": "#10b981",
"warning": "#f59e0b",
"danger": "#ef4444",
"info": "#3b82f6"
},
"chart": {
"primary": "#3b82f6",
"secondary": "#8b5cf6",
"tertiary": "#ec4899",
"quaternary": "#10b981",
"quinary": "#f59e0b"
}
},
"shadows": {
"sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
"md": "0 4px 6px -1px rgb(0 0 0 / 0.1)",
"lg": "0 10px 15px -3px rgb(0 0 0 / 0.1)"
}
},
"dark": {
"colors": {
"background": {
"primary": "#0f172a",
"secondary": "#1e293b",
"tertiary": "#334155"
},
"text": {
"primary": "#f1f5f9",
"secondary": "#cbd5e1",
"tertiary": "#64748b"
},
"border": {
"default": "#334155",
"subtle": "#1e293b"
},
"status": {
"success": "#22c55e",
"warning": "#fbbf24",
"danger": "#f87171",
"info": "#60a5fa"
},
"chart": {
"primary": "#60a5fa",
"secondary": "#a78bfa",
"tertiary": "#f472b6",
"quaternary": "#34d399",
"quinary": "#fbbf24"
}
},
"shadows": {
"sm": "0 1px 2px 0 rgb(0 0 0 / 0.5)",
"md": "0 4px 6px -1px rgb(0 0 0 / 0.5)",
"lg": "0 10px 15px -3px rgb(0 0 0 / 0.5)"
}
}
},
"spacing": {
"xs": "4px",
"sm": "8px",
"md": "16px",
"lg": "24px",
"xl": "32px",
"2xl": "48px"
},
"borderRadius": {
"sm": "4px",
"md": "8px",
"lg": "12px",
"xl": "16px",
"full": "9999px"
},
"typography": {
"fontFamily": {
"sans": "Inter, system-ui, sans-serif",
"mono": "Fira Code, monospace"
},
"fontSize": {
"xs": "12px",
"sm": "14px",
"base": "16px",
"lg": "18px",
"xl": "20px",
"2xl": "24px",
"3xl": "30px",
"4xl": "36px"
},
"fontWeight": {
"normal": 400,
"medium": 500,
"semibold": 600,
"bold": 700
}
},
"animation": {
"duration": {
"fast": "150ms",
"normal": "300ms",
"slow": "500ms"
},
"easing": {
"default": "cubic-bezier(0.4, 0, 0.2, 1)",
"in": "cubic-bezier(0.4, 0, 1, 1)",
"out": "cubic-bezier(0, 0, 0.2, 1)"
}
}
}
{
"widgets": [
{
"id": "basic-kpi",
"type": "kpi",
"name": "Basic KPI Card",
"description": "Simple KPI with value and trend",
"category": "Metrics",
"icon": "chart-bar",
"defaultConfig": {
"title": "Metric Name",
"format": "number",
"aggregation": "sum",
"trend": {
"enabled": true,
"comparison": "previous_period"
}
},
"requiredFields": ["metric", "data_source"],
"optionalFields": ["format", "aggregation", "trend", "sparkline"],
"defaultSize": { "w": 3, "h": 2 },
"minSize": { "w": 2, "h": 2 },
"maxSize": { "w": 6, "h": 4 }
},
{
"id": "advanced-kpi",
"type": "kpi",
"name": "Advanced KPI Card",
"description": "KPI with sparkline, breakdown, and actions",
"category": "Metrics",
"icon": "trending-up",
"defaultConfig": {
"title": "Metric Name",
"format": "currency",
"aggregation": "sum",
"trend": {
"enabled": true,
"comparison": "year_over_year"
},
"sparkline": {
"enabled": true,
"points": 20
},
"breakdown": {
"enabled": true,
"categories": []
}
},
"requiredFields": ["metric", "data_source"],
"defaultSize": { "w": 4, "h": 3 },
"minSize": { "w": 3, "h": 2 },
"maxSize": { "w": 6, "h": 4 }
},
{
"id": "line-chart",
"type": "chart",
"name": "Line Chart",
"description": "Time series line chart",
"category": "Charts",
"icon": "chart-line",
"defaultConfig": {
"chart_type": "line",
"title": "Chart Title",
"axes": {
"x": "date",
"y": "value"
},
"options": {
"showLegend": true,
"showGridLines": true,
"showTooltip": true,
"animation": true
}
},
"requiredFields": ["data_source", "axes"],
"defaultSize": { "w": 6, "h": 4 },
"minSize": { "w": 4, "h": 3 },
"maxSize": { "w": 12, "h": 8 }
},
{
"id": "bar-chart",
"type": "chart",
"name": "Bar Chart",
"description": "Categorical bar chart",
"category": "Charts",
"icon": "chart-bar",
"defaultConfig": {
"chart_type": "bar",
"title": "Chart Title",
"axes": {
"x": "category",
"y": "value"
},
"options": {
"orientation": "vertical",
"stacked": false,
"showValues": true
}
},
"requiredFields": ["data_source", "axes"],
"defaultSize": { "w": 6, "h": 4 },
"minSize": { "w": 4, "h": 3 }
},
{
"id": "area-chart",
"type": "chart",
"name": "Area Chart",
"description": "Filled area chart for trends",
"category": "Charts",
"icon": "chart-area",
"defaultConfig": {
"chart_type": "area",
"title": "Chart Title",
"axes": {
"x": "date",
"y": ["metric1", "metric2"]
},
"options": {
"stacked": true,
"fillOpacity": 0.5
}
},
"requiredFields": ["data_source", "axes"],
"defaultSize": { "w": 8, "h": 4 },
"minSize": { "w": 6, "h": 3 }
},
{
"id": "pie-chart",
"type": "chart",
"name": "Pie Chart",
"description": "Proportional pie chart",
"category": "Charts",
"icon": "chart-pie",
"defaultConfig": {
"chart_type": "pie",
"title": "Chart Title",
"category": "category",
"value": "value",
"options": {
"showLabels": true,
"showPercentages": true
}
},
"requiredFields": ["data_source", "category", "value"],
"defaultSize": { "w": 4, "h": 4 },
"minSize": { "w": 3, "h": 3 }
},
{
"id": "donut-chart",
"type": "chart",
"name": "Donut Chart",
"description": "Donut chart with center metric",
"category": "Charts",
"icon": "chart-donut",
"defaultConfig": {
"chart_type": "donut",
"title": "Chart Title",
"category": "category",
"value": "value",
"centerText": {
"enabled": true,
"metric": "total"
}
},
"requiredFields": ["data_source", "category", "value"],
"defaultSize": { "w": 4, "h": 4 },
"minSize": { "w": 3, "h": 3 }
},
{
"id": "data-table",
"type": "table",
"name": "Data Table",
"description": "Sortable, filterable data table",
"category": "Tables",
"icon": "table",
"defaultConfig": {
"title": "Table Title",
"columns": [],
"features": {
"sorting": true,
"filtering": true,
"pagination": true,
"export": true
},
"pageSize": 10
},
"requiredFields": ["data_source", "columns"],
"defaultSize": { "w": 6, "h": 4 },
"minSize": { "w": 4, "h": 3 }
},
{
"id": "pivot-table",
"type": "table",
"name": "Pivot Table",
"description": "Aggregated pivot table",
"category": "Tables",
"icon": "table-pivot",
"defaultConfig": {
"title": "Pivot Table",
"rows": [],
"columns": [],
"values": [],
"aggregation": "sum"
},
"requiredFields": ["data_source", "rows", "values"],
"defaultSize": { "w": 8, "h": 5 },
"minSize": { "w": 6, "h": 4 }
},
{
"id": "heat-map",
"type": "chart",
"name": "Heat Map",
"description": "Color-coded heat map",
"category": "Charts",
"icon": "grid",
"defaultConfig": {
"chart_type": "heatmap",
"title": "Heat Map",
"x": "x_category",
"y": "y_category",
"value": "intensity",
"colorScale": {
"min": "#f0f0f0",
"max": "#ff0000"
}
},
"requiredFields": ["data_source", "x", "y", "value"],
"defaultSize": { "w": 6, "h": 5 },
"minSize": { "w": 4, "h": 4 }
},
{
"id": "gauge-chart",
"type": "gauge",
"name": "Gauge Chart",
"description": "Radial gauge indicator",
"category": "Charts",
"icon": "gauge",
"defaultConfig": {
"title": "Gauge",
"value": "value",
"min": 0,
"max": 100,
"thresholds": [
{ "value": 33, "color": "red" },
{ "value": 66, "color": "yellow" },
{ "value": 100, "color": "green" }
]
},
"requiredFields": ["data_source", "value", "max"],
"defaultSize": { "w": 3, "h": 3 },
"minSize": { "w": 2, "h": 2 }
},
{
"id": "timeline",
"type": "timeline",
"name": "Timeline",
"description": "Event timeline visualization",
"category": "Time",
"icon": "clock",
"defaultConfig": {
"title": "Timeline",
"dateField": "timestamp",
"eventField": "event",
"categoryField": "category",
"options": {
"showToday": true,
"groupByCategory": false
}
},
"requiredFields": ["data_source", "dateField", "eventField"],
"defaultSize": { "w": 12, "h": 3 },
"minSize": { "w": 8, "h": 2 }
},
{
"id": "map-widget",
"type": "map",
"name": "Geographic Map",
"description": "Interactive map visualization",
"category": "Maps",
"icon": "map",
"defaultConfig": {
"title": "Map",
"mapType": "choropleth",
"geoField": "location",
"valueField": "value",
"center": { "lat": 39.8283, "lng": -98.5795 },
"zoom": 4
},
"requiredFields": ["data_source", "geoField"],
"defaultSize": { "w": 8, "h": 5 },
"minSize": { "w": 6, "h": 4 }
},
{
"id": "scatter-plot",
"type": "chart",
"name": "Scatter Plot",
"description": "X-Y scatter plot",
"category": "Charts",
"icon": "scatter",
"defaultConfig": {
"chart_type": "scatter",
"title": "Scatter Plot",
"axes": {
"x": "x_value",
"y": "y_value"
},
"sizeField": null,
"colorField": null
},
"requiredFields": ["data_source", "axes"],
"defaultSize": { "w": 6, "h": 4 },
"minSize": { "w": 4, "h": 3 }
},
{
"id": "funnel-chart",
"type": "chart",
"name": "Funnel Chart",
"description": "Conversion funnel visualization",
"category": "Charts",
"icon": "funnel",
"defaultConfig": {
"chart_type": "funnel",
"title": "Funnel",
"stages": [],
"valueField": "count",
"showPercentages": true
},
"requiredFields": ["data_source", "stages", "valueField"],
"defaultSize": { "w": 4, "h": 5 },
"minSize": { "w": 3, "h": 4 }
},
{
"id": "text-widget",
"type": "text",
"name": "Text/Markdown",
"description": "Rich text or markdown content",
"category": "Content",
"icon": "text",
"defaultConfig": {
"title": "Text Widget",
"content": "",
"format": "markdown",
"style": {
"fontSize": "14px",
"textAlign": "left"
}
},
"requiredFields": ["content"],
"defaultSize": { "w": 4, "h": 2 },
"minSize": { "w": 2, "h": 1 }
},
{
"id": "image-widget",
"type": "image",
"name": "Image",
"description": "Display image or logo",
"category": "Content",
"icon": "image",
"defaultConfig": {
"src": "",
"alt": "Image",
"fit": "contain",
"link": null
},
"requiredFields": ["src"],
"defaultSize": { "w": 4, "h": 3 },
"minSize": { "w": 2, "h": 2 }
},
{
"id": "filter-select",
"type": "filter",
"name": "Select Filter",
"description": "Single-select dropdown filter",
"category": "Filters",
"icon": "filter",
"defaultConfig": {
"filter_type": "select",
"label": "Filter",
"field": "",
"options": [],
"default": "",
"target_widgets": []
},
"requiredFields": ["field", "options"],
"defaultSize": { "w": 3, "h": 1 },
"minSize": { "w": 2, "h": 1 }
},
{
"id": "filter-multiselect",
"type": "filter",
"name": "Multi-Select Filter",
"description": "Multiple selection filter",
"category": "Filters",
"icon": "filter-multiple",
"defaultConfig": {
"filter_type": "multiselect",
"label": "Filter",
"field": "",
"options": [],
"default": [],
"target_widgets": []
},
"requiredFields": ["field", "options"],
"defaultSize": { "w": 3, "h": 1 },
"minSize": { "w": 2, "h": 1 }
},
{
"id": "filter-daterange",
"type": "filter",
"name": "Date Range Filter",
"description": "Date range picker filter",
"category": "Filters",
"icon": "calendar",
"defaultConfig": {
"filter_type": "date_range",
"label": "Date Range",
"startField": "start_date",
"endField": "end_date",
"presets": [
"today",
"yesterday",
"last_7_days",
"last_30_days",
"this_month",
"last_month",
"custom"
],
"default": "last_30_days",
"target_widgets": []
},
"requiredFields": ["startField", "endField"],
"defaultSize": { "w": 4, "h": 1 },
"minSize": { "w": 3, "h": 1 }
},
{
"id": "progress-bar",
"type": "custom",
"name": "Progress Bar",
"description": "Progress indicator with percentage",
"category": "Indicators",
"icon": "progress",
"defaultConfig": {
"title": "Progress",
"value": 0,
"max": 100,
"showPercentage": true,
"color": "blue",
"segments": []
},
"requiredFields": ["value", "max"],
"defaultSize": { "w": 4, "h": 1 },
"minSize": { "w": 3, "h": 1 }
},
{
"id": "alert-list",
"type": "custom",
"name": "Alert List",
"description": "Real-time alert feed",
"category": "Monitoring",
"icon": "bell",
"defaultConfig": {
"title": "Alerts",
"data_source": "alerts",
"maxItems": 10,
"severities": ["critical", "warning", "info"],
"autoRefresh": true,
"refreshInterval": 5000
},
"requiredFields": ["data_source"],
"defaultSize": { "w": 4, "h": 4 },
"minSize": { "w": 3, "h": 3 }
}
],
"categories": [
{
"id": "metrics",
"name": "Metrics",
"description": "KPI cards and metric displays",
"icon": "chart-bar"
},
{
"id": "charts",
"name": "Charts",
"description": "Data visualizations and graphs",
"icon": "chart-line"
},
{
"id": "tables",
"name": "Tables",
"description": "Data tables and grids",
"icon": "table"
},
{
"id": "filters",
"name": "Filters",
"description": "Interactive filter controls",
"icon": "filter"
},
{
"id": "content",
"name": "Content",
"description": "Text, images, and media",
"icon": "text"
},
{
"id": "maps",
"name": "Maps",
"description": "Geographic visualizations",
"icon": "map"
},
{
"id": "time",
"name": "Time",
"description": "Time-based visualizations",
"icon": "clock"
},
{
"id": "indicators",
"name": "Indicators",
"description": "Progress and status indicators",
"icon": "gauge"
},
{
"id": "monitoring",
"name": "Monitoring",
"description": "Real-time monitoring widgets",
"icon": "activity"
}
],
"metadata": {
"version": "1.0.0",
"total_widgets": 23,
"categories_count": 9
}
}import React, { useState } from 'react';
import GridLayout from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
/**
* Customizable Dashboard with Drag-and-Drop
*
* Features:
* - Drag to reorder widgets
* - Resize widgets
* - Save/restore layout
* - Add/remove widgets
* - Responsive breakpoints
*/
const AVAILABLE_WIDGETS = [
{ id: 'revenue', title: 'Revenue', type: 'kpi' },
{ id: 'users', title: 'Active Users', type: 'kpi' },
{ id: 'chart', title: 'Trend Chart', type: 'chart' },
{ id: 'table', title: 'Recent Orders', type: 'table' },
];
export function CustomizableDashboard() {
const [layout, setLayout] = useState([
{ i: 'revenue', x: 0, y: 0, w: 3, h: 1 },
{ i: 'users', x: 3, y: 0, w: 3, h: 1 },
{ i: 'chart', x: 0, y: 1, w: 6, h: 2 },
{ i: 'table', x: 6, y: 1, w: 6, h: 2 },
]);
const [widgets, setWidgets] = useState(['revenue', 'users', 'chart', 'table']);
const saveLayout = () => {
localStorage.setItem('dashboardLayout', JSON.stringify(layout));
localStorage.setItem('dashboardWidgets', JSON.stringify(widgets));
alert('Layout saved!');
};
const resetLayout = () => {
localStorage.removeItem('dashboardLayout');
localStorage.removeItem('dashboardWidgets');
window.location.reload();
};
const onLayoutChange = (newLayout) => {
setLayout(newLayout);
};
const renderWidget = (widgetId: string) => {
const widget = AVAILABLE_WIDGETS.find((w) => w.id === widgetId);
if (!widget) return null;
return (
<div
key={widgetId}
style={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '8px',
padding: '16px',
height: '100%',
overflow: 'hidden',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600 }}>{widget.title}</h3>
<button
onClick={() => setWidgets(widgets.filter((w) => w !== widgetId))}
style={{ border: 'none', background: 'none', cursor: 'pointer', color: '#ef4444' }}
>
×
</button>
</div>
{widget.type === 'kpi' && (
<div style={{ fontSize: '32px', fontWeight: 700, color: '#3b82f6' }}>
{widgetId === 'revenue' ? '$63K' : '12.5K'}
</div>
)}
{widget.type === 'chart' && (
<div style={{ color: '#64748b', fontSize: '14px' }}>Chart placeholder</div>
)}
{widget.type === 'table' && (
<div style={{ color: '#64748b', fontSize: '14px' }}>Table placeholder</div>
)}
</div>
);
};
return (
<div style={{ padding: '24px', backgroundColor: '#f8fafc', minHeight: '100vh' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '24px' }}>
<h1 style={{ fontSize: '30px', fontWeight: 700 }}>Customizable Dashboard</h1>
<div style={{ display: 'flex', gap: '8px' }}>
<button
onClick={saveLayout}
style={{
padding: '8px 16px',
backgroundColor: '#3b82f6',
color: '#fff',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontWeight: 600,
}}
>
Save Layout
</button>
<button
onClick={resetLayout}
style={{
padding: '8px 16px',
backgroundColor: '#fff',
color: '#64748b',
border: '1px solid #e2e8f0',
borderRadius: '8px',
cursor: 'pointer',
fontWeight: 600,
}}
>
Reset
</button>
</div>
</div>
<GridLayout
className="layout"
layout={layout}
cols={12}
rowHeight={100}
width={1200}
onLayoutChange={onLayoutChange}
draggableHandle=".drag-handle"
isResizable={true}
isDraggable={true}
>
{widgets.map(renderWidget)}
</GridLayout>
<div style={{ marginTop: '24px', padding: '16px', backgroundColor: '#fff', borderRadius: '8px', border: '1px solid #e2e8f0' }}>
<p style={{ fontSize: '14px', color: '#64748b', margin: 0 }}>
<strong>Tip:</strong> Drag widgets to reorder, resize by dragging corners. Changes are saved automatically.
</p>
</div>
</div>
);
}
export default CustomizableDashboard;
import React from 'react';
import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { TrendingUp, TrendingDown, Users, DollarSign, ShoppingCart, Activity } from 'lucide-react';
/**
* Executive Dashboard Example
*
* High-level KPIs for leadership with:
* - Revenue, users, conversion, growth metrics
* - Trend indicators (up/down)
* - Comparison to previous period
* - Revenue trend chart (6 months)
* - Top products bar chart
*/
interface KPICardProps {
title: string;
value: string;
change: number;
changeLabel: string;
icon: React.ReactNode;
positive?: boolean;
}
function KPICard({ title, value, change, changeLabel, icon, positive = true }: KPICardProps) {
const isPositive = change >= 0;
const trendColor = (isPositive && positive) || (!isPositive && !positive) ? '#10b981' : '#ef4444';
return (
<div style={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '12px',
padding: '24px',
display: 'flex',
flexDirection: 'column',
gap: '8px',
}}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span style={{ color: '#64748b', fontSize: '14px', fontWeight: 500 }}>{title}</span>
<div style={{ color: '#94a3b8' }}>{icon}</div>
</div>
<div style={{ fontSize: '32px', fontWeight: 700, color: '#0f172a' }}>
{value}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', fontSize: '14px' }}>
{isPositive ? (
<TrendingUp size={16} color={trendColor} />
) : (
<TrendingDown size={16} color={trendColor} />
)}
<span style={{ color: trendColor, fontWeight: 600 }}>
{Math.abs(change)}%
</span>
<span style={{ color: '#64748b' }}>{changeLabel}</span>
</div>
</div>
);
}
const revenueData = [
{ month: 'Jul', revenue: 42000 },
{ month: 'Aug', revenue: 45000 },
{ month: 'Sep', revenue: 51000 },
{ month: 'Oct', revenue: 49000 },
{ month: 'Nov', revenue: 58000 },
{ month: 'Dec', revenue: 63000 },
];
const productData = [
{ product: 'Pro Plan', sales: 125000 },
{ product: 'Enterprise', sales: 98000 },
{ product: 'Starter', sales: 67000 },
{ product: 'Add-ons', sales: 34000 },
];
export function ExecutiveDashboard() {
return (
<div style={{ padding: '24px', backgroundColor: '#f8fafc', minHeight: '100vh' }}>
<h1 style={{ fontSize: '30px', fontWeight: 700, marginBottom: '24px', color: '#0f172a' }}>
Executive Dashboard
</h1>
{/* KPI Cards */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
gap: '16px',
marginBottom: '24px',
}}>
<KPICard
title="Total Revenue"
value="$63,000"
change={8.6}
changeLabel="vs. last month"
icon={<DollarSign size={24} />}
positive={true}
/>
<KPICard
title="Active Users"
value="12,543"
change={12.4}
changeLabel="vs. last month"
icon={<Users size={24} />}
positive={true}
/>
<KPICard
title="Conversion Rate"
value="3.8%"
change={-2.1}
changeLabel="vs. last month"
icon={<ShoppingCart size={24} />}
positive={true}
/>
<KPICard
title="MRR Growth"
value="18.2%"
change={5.3}
changeLabel="vs. last quarter"
icon={<Activity size={24} />}
positive={true}
/>
</div>
{/* Charts Grid */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
gap: '16px',
}}>
{/* Revenue Trend */}
<div style={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '12px',
padding: '24px',
}}>
<h2 style={{ fontSize: '18px', fontWeight: 600, marginBottom: '16px', color: '#0f172a' }}>
Revenue Trend (6 Months)
</h2>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={revenueData}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="month" stroke="#64748b" />
<YAxis stroke="#64748b" />
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '8px',
}}
/>
<Line
type="monotone"
dataKey="revenue"
stroke="#3b82f6"
strokeWidth={3}
dot={{ fill: '#3b82f6', r: 4 }}
activeDot={{ r: 6 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
{/* Top Products */}
<div style={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '12px',
padding: '24px',
}}>
<h2 style={{ fontSize: '18px', fontWeight: 600, marginBottom: '16px', color: '#0f172a' }}>
Revenue by Product
</h2>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={productData} layout="vertical">
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis type="number" stroke="#64748b" />
<YAxis dataKey="product" type="category" stroke="#64748b" width={100} />
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '8px',
}}
formatter={(value) => `$${value.toLocaleString()}`}
/>
<Bar dataKey="sales" fill="#3b82f6" radius={[0, 8, 8, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
);
}
export default ExecutiveDashboard;
import React, { createContext, useContext, useState, ReactNode } from 'react';
/**
* Dashboard Filter Context
*
* Provides global filter state (date range, region, etc.) to all dashboard widgets.
* When filters change, all connected widgets re-fetch data automatically.
*/
interface FilterState {
dateRange: { start: Date; end: Date };
region: string;
category: string;
}
interface FilterContextType {
filters: FilterState;
setFilters: (filters: Partial<FilterState>) => void;
resetFilters: () => void;
}
const DEFAULT_FILTERS: FilterState = {
dateRange: {
start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30 days ago
end: new Date(),
},
region: 'all',
category: 'all',
};
const FilterContext = createContext<FilterContextType | undefined>(undefined);
export function FilterProvider({ children }: { children: ReactNode }) {
const [filters, setFiltersState] = useState<FilterState>(DEFAULT_FILTERS);
const setFilters = (newFilters: Partial<FilterState>) => {
setFiltersState((prev) => ({ ...prev, ...newFilters }));
};
const resetFilters = () => {
setFiltersState(DEFAULT_FILTERS);
};
return (
<FilterContext.Provider value={{ filters, setFilters, resetFilters }}>
{children}
</FilterContext.Provider>
);
}
export function useFilters() {
const context = useContext(FilterContext);
if (!context) {
throw new Error('useFilters must be used within FilterProvider');
}
return context;
}
// Example widget using filters
function RevenueWidget() {
const { filters } = useFilters();
// Re-fetches when filters change
const { data } = useQuery({
queryKey: ['revenue', filters],
queryFn: () => fetchRevenue(filters.dateRange, filters.region),
});
return <div>Revenue: {data?.total}</div>;
}
// Example filter controls
export function DashboardFilters() {
const { filters, setFilters, resetFilters } = useFilters();
return (
<div style={{
display: 'flex',
gap: '16px',
padding: '16px',
backgroundColor: '#fff',
borderRadius: '8px',
border: '1px solid #e2e8f0',
marginBottom: '24px',
}}>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: 500, marginBottom: '4px' }}>
Date Range
</label>
<input
type="date"
value={filters.dateRange.start.toISOString().split('T')[0]}
onChange={(e) =>
setFilters({
dateRange: { ...filters.dateRange, start: new Date(e.target.value) },
})
}
style={{ padding: '8px', borderRadius: '6px', border: '1px solid #e2e8f0' }}
/>
<span style={{ margin: '0 8px' }}>to</span>
<input
type="date"
value={filters.dateRange.end.toISOString().split('T')[0]}
onChange={(e) =>
setFilters({
dateRange: { ...filters.dateRange, end: new Date(e.target.value) },
})
}
style={{ padding: '8px', borderRadius: '6px', border: '1px solid #e2e8f0' }}
/>
</div>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: 500, marginBottom: '4px' }}>
Region
</label>
<select
value={filters.region}
onChange={(e) => setFilters({ region: e.target.value })}
style={{ padding: '8px', borderRadius: '6px', border: '1px solid #e2e8f0', minWidth: '150px' }}
>
<option value="all">All Regions</option>
<option value="us">North America</option>
<option value="eu">Europe</option>
<option value="asia">Asia Pacific</option>
</select>
</div>
<div>
<label style={{ display: 'block', fontSize: '14px', fontWeight: 500, marginBottom: '4px' }}>
Category
</label>
<select
value={filters.category}
onChange={(e) => setFilters({ category: e.target.value })}
style={{ padding: '8px', borderRadius: '6px', border: '1px solid #e2e8f0', minWidth: '150px' }}
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="home">Home & Garden</option>
</select>
</div>
<button
onClick={resetFilters}
style={{
marginLeft: 'auto',
padding: '8px 16px',
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '6px',
cursor: 'pointer',
fontWeight: 600,
color: '#64748b',
}}
>
Reset Filters
</button>
</div>
);
}
export default DashboardFilters;
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { Activity, Cpu, HardDrive, Wifi } from 'lucide-react';
/**
* System Monitoring Dashboard
*
* Real-time infrastructure monitoring with:
* - Gauge widgets for CPU, memory, disk
* - Live request rate chart (SSE updates)
* - Alert indicators for threshold breaches
* - Color-coded health status
*/
interface GaugeProps {
title: string;
value: number;
max: number;
unit: string;
icon: React.ReactNode;
}
function Gauge({ title, value, max, unit, icon }: GaugeProps) {
const percentage = (value / max) * 100;
const color =
percentage >= 90 ? '#ef4444' :
percentage >= 70 ? '#f59e0b' :
'#10b981';
return (
<div style={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '12px',
padding: '24px',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '16px' }}>
<div style={{ color: '#64748b' }}>{icon}</div>
<span style={{ fontSize: '14px', fontWeight: 500, color: '#64748b' }}>{title}</span>
</div>
<div style={{ position: 'relative', width: '100%', height: '120px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{/* Gauge background */}
<svg width="100%" height="100%" viewBox="0 0 200 120">
{/* Background arc */}
<path
d="M 20 100 A 80 80 0 0 1 180 100"
fill="none"
stroke="#e2e8f0"
strokeWidth="12"
strokeLinecap="round"
/>
{/* Value arc */}
<path
d="M 20 100 A 80 80 0 0 1 180 100"
fill="none"
stroke={color}
strokeWidth="12"
strokeLinecap="round"
strokeDasharray={`${percentage * 2.5} 251`}
/>
</svg>
<div style={{ position: 'absolute', textAlign: 'center' }}>
<div style={{ fontSize: '32px', fontWeight: 700, color }}>
{value.toFixed(1)}
<span style={{ fontSize: '20px', color: '#64748b' }}>{unit}</span>
</div>
<div style={{ fontSize: '14px', color: '#94a3b8' }}>
of {max}{unit}
</div>
</div>
</div>
</div>
);
}
export function MonitoringDashboard() {
const [metricsData, setMetricsData] = useState([
{ time: '10:00', requests: 1200 },
{ time: '10:05', requests: 1350 },
{ time: '10:10', requests: 1180 },
{ time: '10:15', requests: 1420 },
{ time: '10:20', requests: 1560 },
{ time: '10:25', requests: 1380 },
]);
// Simulate real-time updates (replace with actual SSE)
useEffect(() => {
const interval = setInterval(() => {
setMetricsData((prev) => {
const newData = [...prev.slice(1)];
const lastTime = prev[prev.length - 1].time;
const [hours, minutes] = lastTime.split(':').map(Number);
const newMinutes = (minutes + 5) % 60;
const newTime = `${hours}:${newMinutes.toString().padStart(2, '0')}`;
newData.push({
time: newTime,
requests: Math.floor(1000 + Math.random() * 600),
});
return newData;
});
}, 5000);
return () => clearInterval(interval);
}, []);
return (
<div style={{ padding: '24px', backgroundColor: '#f8fafc', minHeight: '100vh' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '24px' }}>
<h1 style={{ fontSize: '30px', fontWeight: 700, color: '#0f172a' }}>
System Monitoring
</h1>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '8px 16px',
backgroundColor: '#10b981',
borderRadius: '8px',
color: '#fff',
fontSize: '14px',
fontWeight: 600,
}}>
<div style={{ width: '8px', height: '8px', backgroundColor: '#fff', borderRadius: '50%' }} />
All Systems Operational
</div>
</div>
{/* Gauge Widgets */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
gap: '16px',
marginBottom: '24px',
}}>
<Gauge
title="CPU Usage"
value={45.2}
max={100}
unit="%"
icon={<Cpu size={20} />}
/>
<Gauge
title="Memory Usage"
value={68.5}
max={100}
unit="%"
icon={<HardDrive size={20} />}
/>
<Gauge
title="Disk Usage"
value={52.3}
max={100}
unit="%"
icon={<HardDrive size={20} />}
/>
<Gauge
title="Network I/O"
value={234}
max={1000}
unit=" Mbps"
icon={<Wifi size={20} />}
/>
</div>
{/* Request Rate Chart */}
<div style={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '12px',
padding: '24px',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '16px' }}>
<Activity size={20} color="#64748b" />
<h2 style={{ fontSize: '18px', fontWeight: 600, color: '#0f172a', margin: 0 }}>
Request Rate (Real-time)
</h2>
<div style={{
marginLeft: 'auto',
padding: '4px 12px',
backgroundColor: '#eff6ff',
borderRadius: '6px',
fontSize: '12px',
color: '#3b82f6',
fontWeight: 600,
}}>
LIVE
</div>
</div>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={metricsData}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="time" stroke="#64748b" />
<YAxis stroke="#64748b" label={{ value: 'Requests', angle: -90, position: 'insideLeft' }} />
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e2e8f0',
borderRadius: '8px',
}}
/>
<Line
type="monotone"
dataKey="requests"
stroke="#3b82f6"
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
);
}
export default MonitoringDashboard;
/**
* Sales Analytics Dashboard
* Complete sales dashboard with revenue KPIs, product charts, and customer tables
*/
import React, { useState, useEffect, useContext } from 'react';
import {
Card,
Grid,
Title,
Text,
Metric,
BadgeDelta,
AreaChart,
BarChart,
DonutChart,
Table,
TableHead,
TableRow,
TableHeaderCell,
TableBody,
TableCell,
DateRangePicker,
Select,
SelectItem,
MultiSelect,
MultiSelectItem
} from '@tremor/react';
// Dashboard Context for Global Filters
const DashboardContext = React.createContext({
filters: {
dateRange: { start: new Date(), end: new Date() },
regions: [],
products: [],
salesReps: []
},
setFilters: () => {},
refreshInterval: 30000
});
// Main Sales Dashboard Component
export function SalesDashboard() {
const [filters, setFilters] = useState({
dateRange: {
start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
end: new Date()
},
regions: [],
products: [],
salesReps: []
});
const [dashboardData, setDashboardData] = useState(null);
const [loading, setLoading] = useState(true);
// Fetch dashboard data based on filters
useEffect(() => {
fetchDashboardData(filters).then(data => {
setDashboardData(data);
setLoading(false);
});
}, [filters]);
if (loading) {
return <DashboardSkeleton />;
}
return (
<DashboardContext.Provider value={{ filters, setFilters, refreshInterval: 30000 }}>
<div className="p-6 bg-gray-50 min-h-screen">
{/* Dashboard Header */}
<div className="mb-6">
<Title>Sales Analytics Dashboard</Title>
<Text>Track revenue, products, and customer metrics</Text>
</div>
{/* Global Filters */}
<Card className="mb-6">
<div className="flex gap-4 flex-wrap">
<DateRangePicker
value={filters.dateRange}
onValueChange={(value) => setFilters({ ...filters, dateRange: value })}
className="max-w-xs"
/>
<MultiSelect
placeholder="Select Regions"
value={filters.regions}
onValueChange={(value) => setFilters({ ...filters, regions: value })}
>
<MultiSelectItem value="north">North</MultiSelectItem>
<MultiSelectItem value="south">South</MultiSelectItem>
<MultiSelectItem value="east">East</MultiSelectItem>
<MultiSelectItem value="west">West</MultiSelectItem>
</MultiSelect>
<MultiSelect
placeholder="Select Products"
value={filters.products}
onValueChange={(value) => setFilters({ ...filters, products: value })}
>
<MultiSelectItem value="product-a">Product A</MultiSelectItem>
<MultiSelectItem value="product-b">Product B</MultiSelectItem>
<MultiSelectItem value="product-c">Product C</MultiSelectItem>
</MultiSelect>
</div>
</Card>
{/* KPI Cards */}
<Grid numItemsSm={2} numItemsLg={4} className="gap-6 mb-6">
<RevenueKPI data={dashboardData.revenue} />
<OrdersKPI data={dashboardData.orders} />
<CustomersKPI data={dashboardData.customers} />
<ConversionKPI data={dashboardData.conversion} />
</Grid>
{/* Charts Row */}
<Grid numItemsLg={2} className="gap-6 mb-6">
<RevenueChart data={dashboardData.revenueHistory} />
<ProductPerformance data={dashboardData.products} />
</Grid>
{/* Sales by Region and Top Products */}
<Grid numItemsLg={3} className="gap-6 mb-6">
<RegionalSales data={dashboardData.regions} />
<TopProducts data={dashboardData.topProducts} />
<SalesTeamPerformance data={dashboardData.salesTeam} />
</Grid>
{/* Recent Transactions Table */}
<Card>
<Title>Recent Transactions</Title>
<TransactionsTable data={dashboardData.transactions} />
</Card>
</div>
</DashboardContext.Provider>
);
}
// KPI Components
function RevenueKPI({ data }) {
return (
<Card>
<Text>Total Revenue</Text>
<Metric>${data.value.toLocaleString()}</Metric>
<div className="flex items-center justify-between mt-4">
<BadgeDelta deltaType={data.trend > 0 ? 'increase' : 'decrease'}>
{data.trend > 0 ? '+' : ''}{data.trend}%
</BadgeDelta>
<Text className="text-xs">vs last period</Text>
</div>
<Sparkline data={data.sparkline} className="mt-4 h-10" />
</Card>
);
}
function OrdersKPI({ data }) {
return (
<Card>
<Text>Total Orders</Text>
<Metric>{data.value.toLocaleString()}</Metric>
<div className="flex items-center justify-between mt-4">
<BadgeDelta deltaType={data.trend > 0 ? 'increase' : 'decrease'}>
{data.trend > 0 ? '+' : ''}{data.trend}%
</BadgeDelta>
<Text className="text-xs">vs last period</Text>
</div>
</Card>
);
}
function CustomersKPI({ data }) {
return (
<Card>
<Text>New Customers</Text>
<Metric>{data.value.toLocaleString()}</Metric>
<div className="flex items-center justify-between mt-4">
<BadgeDelta deltaType={data.trend > 0 ? 'increase' : 'decrease'}>
{data.trend > 0 ? '+' : ''}{data.trend}%
</BadgeDelta>
<Text className="text-xs">vs last period</Text>
</div>
</Card>
);
}
function ConversionKPI({ data }) {
return (
<Card>
<Text>Conversion Rate</Text>
<Metric>{data.value}%</Metric>
<div className="flex items-center justify-between mt-4">
<BadgeDelta deltaType={data.trend > 0 ? 'increase' : 'decrease'}>
{data.trend > 0 ? '+' : ''}{data.trend} pp
</BadgeDelta>
<Text className="text-xs">vs last period</Text>
</div>
<ProgressBar value={data.value} className="mt-4" />
</Card>
);
}
// Chart Components
function RevenueChart({ data }) {
const { filters } = useContext(DashboardContext);
return (
<Card>
<Title>Revenue Trend</Title>
<Text>Daily revenue over selected period</Text>
<AreaChart
className="h-72 mt-4"
data={data}
index="date"
categories={["revenue", "target"]}
colors={["blue", "gray"]}
valueFormatter={(value) => `$${(value / 1000).toFixed(1)}k`}
showLegend
showGridLines
showAnimation
/>
</Card>
);
}
function ProductPerformance({ data }) {
return (
<Card>
<Title>Product Performance</Title>
<Text>Revenue by product category</Text>
<BarChart
className="h-72 mt-4"
data={data}
index="product"
categories={["revenue", "units"]}
colors={["blue", "cyan"]}
valueFormatter={(value) => `$${(value / 1000).toFixed(1)}k`}
showLegend
stack={false}
/>
</Card>
);
}
function RegionalSales({ data }) {
return (
<Card>
<Title>Sales by Region</Title>
<DonutChart
className="h-60 mt-4"
data={data}
category="sales"
index="region"
valueFormatter={(value) => `$${(value / 1000).toFixed(0)}k`}
colors={["blue", "cyan", "indigo", "violet"]}
showLabel
showAnimation
/>
<div className="mt-4">
{data.map((item) => (
<div key={item.region} className="flex justify-between py-1">
<Text>{item.region}</Text>
<Text className="font-medium">${(item.sales / 1000).toFixed(0)}k</Text>
</div>
))}
</div>
</Card>
);
}
function TopProducts({ data }) {
return (
<Card>
<Title>Top Products</Title>
<Table className="mt-4">
<TableHead>
<TableRow>
<TableHeaderCell>Product</TableHeaderCell>
<TableHeaderCell>Revenue</TableHeaderCell>
<TableHeaderCell>Growth</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{data.slice(0, 5).map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>${(item.revenue / 1000).toFixed(1)}k</TableCell>
<TableCell>
<BadgeDelta deltaType={item.growth > 0 ? 'increase' : 'decrease'}>
{item.growth}%
</BadgeDelta>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
);
}
function SalesTeamPerformance({ data }) {
return (
<Card>
<Title>Sales Team Performance</Title>
<div className="mt-4 space-y-4">
{data.slice(0, 5).map((rep) => (
<div key={rep.id}>
<div className="flex justify-between mb-1">
<Text>{rep.name}</Text>
<Text className="font-medium">${(rep.sales / 1000).toFixed(0)}k</Text>
</div>
<ProgressBar value={(rep.sales / rep.target) * 100} color="blue" />
<Text className="text-xs mt-1">{((rep.sales / rep.target) * 100).toFixed(0)}% of target</Text>
</div>
))}
</div>
</Card>
);
}
// Transactions Table
function TransactionsTable({ data }) {
const [page, setPage] = useState(0);
const pageSize = 10;
const paginatedData = data.slice(page * pageSize, (page + 1) * pageSize);
return (
<>
<Table className="mt-4">
<TableHead>
<TableRow>
<TableHeaderCell>Transaction ID</TableHeaderCell>
<TableHeaderCell>Date</TableHeaderCell>
<TableHeaderCell>Customer</TableHeaderCell>
<TableHeaderCell>Product</TableHeaderCell>
<TableHeaderCell>Amount</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{paginatedData.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell>{transaction.id}</TableCell>
<TableCell>{new Date(transaction.date).toLocaleDateString()}</TableCell>
<TableCell>{transaction.customer}</TableCell>
<TableCell>{transaction.product}</TableCell>
<TableCell>${transaction.amount.toLocaleString()}</TableCell>
<TableCell>
<Badge color={transaction.status === 'completed' ? 'green' : 'yellow'}>
{transaction.status}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{/* Pagination */}
<div className="flex justify-between items-center mt-4">
<Text>
Showing {page * pageSize + 1} to {Math.min((page + 1) * pageSize, data.length)} of {data.length}
</Text>
<div className="flex gap-2">
<button
onClick={() => setPage(Math.max(0, page - 1))}
disabled={page === 0}
className="px-3 py-1 border rounded disabled:opacity-50"
>
Previous
</button>
<button
onClick={() => setPage(page + 1)}
disabled={(page + 1) * pageSize >= data.length}
className="px-3 py-1 border rounded disabled:opacity-50"
>
Next
</button>
</div>
</div>
</>
);
}
// Loading Skeleton
function DashboardSkeleton() {
return (
<div className="p-6 bg-gray-50 min-h-screen">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/3 mb-6"></div>
<Grid numItemsLg={4} className="gap-6 mb-6">
{[1, 2, 3, 4].map((i) => (
<Card key={i}>
<div className="h-4 bg-gray-200 rounded w-1/2 mb-2"></div>
<div className="h-8 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/3"></div>
</Card>
))}
</Grid>
</div>
</div>
);
}
// Sparkline Component
function Sparkline({ data, className = '' }) {
const max = Math.max(...data);
const min = Math.min(...data);
const range = max - min || 1;
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * 100;
const y = 100 - ((value - min) / range) * 100;
return `${x},${y}`;
}).join(' ');
return (
<svg className={`w-full ${className}`} viewBox="0 0 100 100" preserveAspectRatio="none">
<polyline
points={points}
fill="none"
stroke="currentColor"
strokeWidth="2"
className="text-blue-500"
/>
</svg>
);
}
// Badge Component
function Badge({ children, color = 'gray' }) {
const colors = {
green: 'bg-green-100 text-green-800',
yellow: 'bg-yellow-100 text-yellow-800',
red: 'bg-red-100 text-red-800',
gray: 'bg-gray-100 text-gray-800'
};
return (
<span className={`px-2 py-1 text-xs font-medium rounded ${colors[color]}`}>
{children}
</span>
);
}
// Progress Bar Component
function ProgressBar({ value, color = 'blue', className = '' }) {
return (
<div className={`w-full bg-gray-200 rounded-full h-2 ${className}`}>
<div
className={`h-2 rounded-full bg-${color}-500`}
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
/>
</div>
);
}
// Mock data fetching function
async function fetchDashboardData(filters) {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
return {
revenue: {
value: 1245832,
trend: 15.3,
sparkline: [100, 120, 115, 140, 155, 145, 160, 180, 175, 190, 210, 205]
},
orders: {
value: 3421,
trend: 8.7
},
customers: {
value: 892,
trend: -2.3
},
conversion: {
value: 3.8,
trend: 0.5
},
revenueHistory: generateRevenueHistory(),
products: generateProductData(),
regions: generateRegionalData(),
topProducts: generateTopProducts(),
salesTeam: generateSalesTeamData(),
transactions: generateTransactions()
};
}
// Data generation helpers
function generateRevenueHistory() {
const data = [];
const now = new Date();
for (let i = 30; i >= 0; i--) {
const date = new Date(now);
date.setDate(date.getDate() - i);
data.push({
date: date.toISOString().split('T')[0],
revenue: Math.floor(Math.random() * 50000) + 30000,
target: 40000
});
}
return data;
}
function generateProductData() {
return [
{ product: 'Product A', revenue: 125000, units: 1250 },
{ product: 'Product B', revenue: 98000, units: 890 },
{ product: 'Product C', revenue: 87000, units: 1100 },
{ product: 'Product D', revenue: 65000, units: 450 }
];
}
function generateRegionalData() {
return [
{ region: 'North', sales: 425000 },
{ region: 'South', sales: 380000 },
{ region: 'East', sales: 290000 },
{ region: 'West', sales: 150832 }
];
}
function generateTopProducts() {
return [
{ id: 1, name: 'Premium Widget', revenue: 234000, growth: 23.5 },
{ id: 2, name: 'Standard Widget', revenue: 189000, growth: 15.2 },
{ id: 3, name: 'Basic Widget', revenue: 145000, growth: 8.7 },
{ id: 4, name: 'Deluxe Widget', revenue: 98000, growth: -5.3 },
{ id: 5, name: 'Compact Widget', revenue: 87000, growth: 12.1 }
];
}
function generateSalesTeamData() {
return [
{ id: 1, name: 'John Smith', sales: 145000, target: 150000 },
{ id: 2, name: 'Jane Doe', sales: 132000, target: 140000 },
{ id: 3, name: 'Mike Johnson', sales: 118000, target: 130000 },
{ id: 4, name: 'Sarah Williams', sales: 105000, target: 120000 },
{ id: 5, name: 'Tom Brown', sales: 95000, target: 110000 }
];
}
function generateTransactions() {
const transactions = [];
const products = ['Widget A', 'Widget B', 'Widget C', 'Widget D'];
const customers = ['Acme Corp', 'GlobalTech', 'MegaCorp', 'TechStart', 'Enterprise Inc'];
const statuses = ['completed', 'pending', 'completed', 'completed'];
for (let i = 0; i < 50; i++) {
transactions.push({
id: `TRX-${1000 + i}`,
date: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
customer: customers[Math.floor(Math.random() * customers.length)],
product: products[Math.floor(Math.random() * products.length)],
amount: Math.floor(Math.random() * 5000) + 500,
status: statuses[Math.floor(Math.random() * statuses.length)]
});
}
return transactions.sort((a, b) => b.date - a.date);
}
export default SalesDashboard;import { Card, Title, Text, Metric, Flex, BadgeDelta, AreaChart, BarList } from '@tremor/react';
/**
* Tremor Dashboard Example
*
* Uses Tremor library for pre-built dashboard components.
* Tremor provides beautiful, accessible components out-of-the-box.
*
* Install: npm install @tremor/react
*/
const salesData = [
{ month: 'Jan', Sales: 4500, Target: 4000 },
{ month: 'Feb', Sales: 3800, Target: 4000 },
{ month: 'Mar', Sales: 5100, Target: 4500 },
{ month: 'Apr', Sales: 4600, Target: 4500 },
{ month: 'May', Sales: 5400, Target: 5000 },
{ month: 'Jun', Sales: 6200, Target: 5500 },
];
const topProducts = [
{ name: 'Pro Plan', value: 45000 },
{ name: 'Enterprise', value: 38000 },
{ name: 'Starter', value: 29000 },
{ name: 'Add-ons', value: 12000 },
];
export function TremorDashboard() {
return (
<div style={{ padding: '24px', backgroundColor: '#f9fafb', minHeight: '100vh' }}>
<Title>Sales Dashboard</Title>
<Text>Overview of sales performance and key metrics</Text>
{/* KPI Cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '16px', marginTop: '24px' }}>
<Card>
<Flex alignItems="start">
<div>
<Text>Revenue</Text>
<Metric>$63,000</Metric>
</div>
<BadgeDelta deltaType="increase">8.6%</BadgeDelta>
</Flex>
</Card>
<Card>
<Flex alignItems="start">
<div>
<Text>Active Users</Text>
<Metric>12,543</Metric>
</div>
<BadgeDelta deltaType="increase">12.4%</BadgeDelta>
</Flex>
</Card>
<Card>
<Flex alignItems="start">
<div>
<Text>Conversion Rate</Text>
<Metric>3.8%</Metric>
</div>
<BadgeDelta deltaType="decrease">-2.1%</BadgeDelta>
</Flex>
</Card>
</div>
{/* Charts */}
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '16px', marginTop: '24px' }}>
<Card>
<Title>Sales vs Target</Title>
<AreaChart
className="mt-4 h-72"
data={salesData}
index="month"
categories={['Sales', 'Target']}
colors={['blue', 'gray']}
valueFormatter={(number) => `$${(number / 1000).toFixed(1)}K`}
yAxisWidth={48}
/>
</Card>
<Card>
<Title>Top Products</Title>
<Flex className="mt-4">
<Text>Product</Text>
<Text>Revenue</Text>
</Flex>
<BarList
data={topProducts}
className="mt-2"
valueFormatter={(number) => `$${(number / 1000).toFixed(0)}K`}
/>
</Card>
</div>
</div>
);
}
export default TremorDashboard;
skill: "creating-dashboards"
version: "1.0"
domain: "frontend"
# Base outputs required for all dashboard implementations
base_outputs:
- path: "components/"
must_contain: ["widgets/", "layouts/", "filters/"]
reason: "Core dashboard component structure (widgets, layouts, filters)"
- path: "components/widgets/"
must_contain: []
reason: "Individual widget components (KPI cards, charts, tables)"
- path: "components/layouts/"
must_contain: []
reason: "Dashboard layout components (grid systems, responsive containers)"
- path: "context/"
must_contain: []
reason: "Global state management for filters and dashboard coordination"
- path: "package.json"
must_contain: ["dependencies", "scripts"]
reason: "Project dependencies and build scripts"
# Conditional outputs based on configuration
conditional_outputs:
maturity:
starter:
- path: "components/KPICard.tsx"
must_contain: ["interface KPICard", "value:", "trend:"]
reason: "Basic KPI card component with value and trend"
- path: "components/SimpleDashboard.tsx"
must_contain: ["Grid", "KPICard"]
reason: "Simple static dashboard layout with KPI cards"
- path: "package.json"
must_contain: ["@tremor/react"]
reason: "Tremor library for quick dashboard development"
- path: "pages/Dashboard.tsx"
must_contain: ["Card", "Metric", "Text"]
reason: "Basic dashboard page using Tremor components"
- path: "styles/dashboard.css"
must_contain: [".dashboard", ".widget", "grid"]
reason: "Basic dashboard styling with grid layout"
intermediate:
- path: "components/widgets/KPICard.tsx"
must_contain: ["value", "trend", "sparkline", "onClick"]
reason: "Enhanced KPI card with sparkline and interactivity"
- path: "components/widgets/ChartWidget.tsx"
must_contain: ["AreaChart", "loading", "error"]
reason: "Chart widget with loading and error states"
- path: "components/widgets/TableWidget.tsx"
must_contain: ["pagination", "sorting"]
reason: "Table widget with basic interactivity"
- path: "components/filters/FilterPanel.tsx"
must_contain: ["DateRangePicker", "onChange"]
reason: "Filter panel with date range selection"
- path: "context/DashboardContext.tsx"
must_contain: ["createContext", "filters", "setFilters"]
reason: "Context for coordinating filters across widgets"
- path: "hooks/useSSEUpdates.ts"
must_contain: ["EventSource", "useEffect"]
reason: "Server-sent events hook for real-time updates"
- path: "utils/formatters.ts"
must_contain: ["formatLargeNumber", "formatCurrency", "formatPercentage"]
reason: "Number formatting utilities for metrics"
- path: "components/DashboardGrid.tsx"
must_contain: ["LazyLoad", "WidgetSkeleton"]
reason: "Lazy loading grid for performance optimization"
- path: "package.json"
must_contain: ["@tremor/react", "react-lazyload"]
reason: "Dashboard libraries with lazy loading support"
advanced:
- path: "components/widgets/KPICard.tsx"
must_contain: ["value", "trend", "sparkline", "comparison", "status", "icon"]
reason: "Full-featured KPI card with all visual elements"
- path: "components/widgets/ChartWidget.tsx"
must_contain: ["AreaChart", "BarChart", "LineChart", "loading", "error", "export"]
reason: "Multi-type chart widget with export capability"
- path: "components/widgets/TableWidget.tsx"
must_contain: ["pagination", "sorting", "filtering", "export"]
reason: "Advanced table widget with full interactivity"
- path: "components/layouts/CustomizableGrid.tsx"
must_contain: ["react-grid-layout", "Responsive", "onLayoutChange", "localStorage"]
reason: "Drag-and-drop customizable dashboard grid with persistence"
- path: "components/layouts/WidgetCatalog.tsx"
must_contain: ["AVAILABLE_WIDGETS", "addWidget", "removeWidget"]
reason: "Widget catalog for dashboard customization"
- path: "components/filters/AdvancedFilterPanel.tsx"
must_contain: ["DateRangePicker", "MultiSelect", "SearchInput", "FilterPresets"]
reason: "Advanced filter panel with multiple filter types and presets"
- path: "context/DashboardContext.tsx"
must_contain: ["createContext", "filters", "setFilters", "refreshInterval", "autoRefresh"]
reason: "Full dashboard context with auto-refresh support"
- path: "hooks/useWebSocket.ts"
must_contain: ["WebSocket", "reconnect", "heartbeat"]
reason: "WebSocket hook with reconnection and heartbeat"
- path: "hooks/useSmartPolling.ts"
must_contain: ["setInterval", "document.hidden", "pause"]
reason: "Smart polling with tab visibility detection"
- path: "hooks/useCachedWidget.ts"
must_contain: ["useRef", "cache", "ttl", "timestamp"]
reason: "Widget-level caching hook for performance"
- path: "utils/exportDashboard.ts"
must_contain: ["exportToPDF", "exportToImage", "exportToCSV"]
reason: "Dashboard export utilities for multiple formats"
- path: "utils/performanceMonitor.ts"
must_contain: ["measureRenderTime", "trackDataFetch", "reportMetrics"]
reason: "Performance monitoring and analytics"
- path: "config/dashboardTemplates.json"
must_contain: ["templates", "widgets", "layouts"]
reason: "Pre-built dashboard template configurations"
- path: "package.json"
must_contain: ["react-grid-layout", "@tremor/react", "react-lazyload"]
reason: "Full dashboard toolkit with customization support"
frontend_framework:
react:
- path: "components/DashboardProvider.tsx"
must_contain: ["createContext", "useContext", "useState", "useEffect"]
reason: "React Context-based dashboard provider"
- path: "hooks/useDashboard.ts"
must_contain: ["useContext", "DashboardContext"]
reason: "React hook for accessing dashboard context"
- path: "components/widgets/KPICard.tsx"
must_contain: ["React.FC", "interface", "props"]
reason: "React functional component with TypeScript"
- path: "tsconfig.json"
must_contain: ["jsx", "react"]
reason: "TypeScript configuration for React"
vue:
- path: "components/DashboardProvider.vue"
must_contain: ["provide", "inject", "ref", "reactive"]
reason: "Vue Composition API dashboard provider"
- path: "composables/useDashboard.ts"
must_contain: ["inject", "computed"]
reason: "Vue composable for dashboard state"
- path: "components/widgets/KPICard.vue"
must_contain: ["<template>", "<script setup>", "defineProps"]
reason: "Vue 3 component with script setup"
svelte:
- path: "stores/dashboardStore.ts"
must_contain: ["writable", "derived", "subscribe"]
reason: "Svelte store for dashboard state management"
- path: "components/widgets/KPICard.svelte"
must_contain: ["<script>", "export let", "$"]
reason: "Svelte component with reactive props"
styling:
tailwind:
- path: "tailwind.config.js"
must_contain: ["theme:", "extend:", "colors:", "spacing:"]
reason: "Tailwind configuration with custom dashboard tokens"
- path: "components/widgets/KPICard.tsx"
must_contain: ["className", "grid", "flex"]
reason: "Tailwind utility classes for styling"
- path: "styles/dashboard.css"
must_contain: ["@tailwind", "@layer"]
reason: "Tailwind directives and custom layers"
css_modules:
- path: "components/widgets/KPICard.module.css"
must_contain: [".card", ".value", ".trend"]
reason: "CSS Modules for component styling"
- path: "components/widgets/KPICard.tsx"
must_contain: ["import styles from", "styles.card"]
reason: "CSS Module imports in React components"
styled_components:
- path: "components/widgets/StyledKPICard.tsx"
must_contain: ["styled.", "props.theme", "css"]
reason: "Styled-components for dynamic styling"
- path: "theme/dashboardTheme.ts"
must_contain: ["export const dashboardTheme", "colors", "spacing"]
reason: "Theme configuration for styled-components"
scss:
- path: "styles/dashboard.scss"
must_contain: ["$dashboard-", "@mixin", "@import"]
reason: "SCSS variables and mixins for dashboard styling"
- path: "styles/_variables.scss"
must_contain: ["$widget-bg:", "$kpi-value-size:"]
reason: "SCSS variables for dashboard design tokens"
state_management:
context:
- path: "context/DashboardContext.tsx"
must_contain: ["createContext", "DashboardContext.Provider", "value"]
reason: "React Context for dashboard state"
- path: "hooks/useDashboard.ts"
must_contain: ["useContext", "DashboardContext"]
reason: "Hook to consume dashboard context"
zustand:
- path: "stores/useDashboardStore.ts"
must_contain: ["create", "persist", "filters", "setFilters"]
reason: "Zustand store with persistence for dashboard state"
- path: "components/Dashboard.tsx"
must_contain: ["useDashboardStore", "filters", "setFilters"]
reason: "Component consuming Zustand dashboard store"
redux:
- path: "store/dashboardSlice.ts"
must_contain: ["createSlice", "setFilters", "updateWidget"]
reason: "Redux slice for dashboard state management"
- path: "store/index.ts"
must_contain: ["configureStore", "dashboardReducer"]
reason: "Redux store configuration with dashboard slice"
pinia:
- path: "stores/dashboardStore.ts"
must_contain: ["defineStore", "state", "actions", "getters"]
reason: "Pinia store for Vue dashboard state"
- path: "components/Dashboard.vue"
must_contain: ["useDashboardStore", "storeToRefs"]
reason: "Vue component consuming Pinia store"
# Scaffolding files that should be created as starting points
scaffolding:
- path: "components/widgets/KPICard.tsx"
reason: "Initialize basic KPI card component"
- path: "components/widgets/ChartWidget.tsx"
reason: "Initialize chart widget component"
- path: "components/layouts/DashboardGrid.tsx"
reason: "Initialize dashboard grid layout component"
- path: "components/filters/FilterPanel.tsx"
reason: "Initialize filter panel component"
- path: "context/DashboardContext.tsx"
reason: "Initialize dashboard context for state management"
- path: "hooks/useDashboard.ts"
reason: "Initialize dashboard context hook"
- path: "utils/formatters.ts"
reason: "Initialize number formatting utilities"
- path: "types/dashboard.ts"
reason: "Initialize TypeScript types and interfaces"
- path: "pages/Dashboard.tsx"
reason: "Initialize main dashboard page component"
- path: "styles/dashboard.css"
reason: "Initialize dashboard base styles"
- path: "config/widgets.ts"
reason: "Initialize widget catalog configuration"
- path: "package.json"
reason: "Initialize npm dependencies and scripts"
- path: "tsconfig.json"
reason: "TypeScript configuration"
- path: ".gitignore"
reason: "Ignore node_modules and build artifacts"
- path: "README.md"
reason: "Document dashboard architecture and usage"
# Metadata
metadata:
primary_blueprints: ["dashboard", "frontend"]
contributes_to:
- "Dashboard layout and grid systems"
- "KPI cards with trends and sparklines"
- "Real-time data updates (SSE, WebSocket, polling)"
- "Cross-widget filter coordination"
- "Widget catalog and customization"
- "Performance optimization (lazy loading, caching, parallel fetching)"
- "Export capabilities (PDF, images, data)"
- "Responsive design (desktop, tablet, mobile)"
common_patterns:
- "KPI cards with value, trend, comparison, and sparkline"
- "Widget-based architecture with reusable components"
- "Global filter context coordinating across all widgets"
- "Real-time updates via Server-Sent Events (SSE)"
- "Lazy loading for performance with skeleton states"
- "Drag-and-drop grid customization with react-grid-layout"
- "Dashboard templates for quick setup"
- "Tremor library for pre-built dashboard components"
- "Widget-level caching with TTL expiration"
- "Smart polling with tab visibility detection"
- "Responsive breakpoints (desktop >1200px, tablet 768-1200px, mobile <768px)"
integration_points:
data_viz: "Uses chart components for visualization widgets (AreaChart, BarChart, LineChart)"
tables: "Uses data grid components for table widgets with sorting/filtering"
design_tokens: "Uses design tokens for consistent theming and styling"
forms: "Uses form components for filter inputs (DateRangePicker, MultiSelect)"
feedback: "Uses feedback components for loading states and error messages"
typical_directory_structure: |
project/
├── components/
│ ├── widgets/
│ │ ├── KPICard.tsx # KPI card with trends
│ │ ├── ChartWidget.tsx # Chart visualization widget
│ │ ├── TableWidget.tsx # Data table widget
│ │ └── WidgetContainer.tsx # Common widget wrapper
│ ├── layouts/
│ │ ├── DashboardGrid.tsx # Static grid layout
│ │ ├── CustomizableGrid.tsx # Drag-and-drop grid
│ │ └── ResponsiveLayout.tsx # Responsive container
│ ├── filters/
│ │ ├── FilterPanel.tsx # Main filter panel
│ │ ├── DateRangePicker.tsx # Date range filter
│ │ └── CategoryFilter.tsx # Category multi-select
│ └── common/
│ ├── WidgetSkeleton.tsx # Loading skeleton
│ └── ErrorBoundary.tsx # Error handling
├── context/
│ └── DashboardContext.tsx # Global dashboard state
├── hooks/
│ ├── useDashboard.ts # Dashboard context hook
│ ├── useSSEUpdates.ts # Server-sent events
│ ├── useWebSocket.ts # WebSocket connection
│ ├── useSmartPolling.ts # Smart polling
│ └── useCachedWidget.ts # Widget caching
├── utils/
│ ├── formatters.ts # Number formatting
│ ├── exportDashboard.ts # Export utilities
│ └── performanceMonitor.ts # Performance tracking
├── types/
│ └── dashboard.ts # TypeScript interfaces
├── config/
│ ├── widgets.ts # Widget catalog
│ └── templates.ts # Dashboard templates
├── pages/
│ ├── Dashboard.tsx # Main dashboard page
│ ├── SalesDashboard.tsx # Sales analytics
│ └── MonitoringDashboard.tsx # Real-time monitoring
├── styles/
│ ├── dashboard.css # Base styles
│ └── widgets.css # Widget styles
├── package.json # Dependencies
├── tsconfig.json # TypeScript config
└── README.md # Documentation
tools_required:
- name: "@tremor/react"
version: "^3.0.0"
purpose: "Pre-built dashboard components (KPI cards, charts, tables)"
install: "npm install @tremor/react"
- name: "react-grid-layout"
version: "^1.4.0"
purpose: "Drag-and-drop grid system for customizable dashboards"
install: "npm install react-grid-layout"
- name: "react-lazyload"
version: "^3.2.0"
purpose: "Lazy loading for performance optimization"
install: "npm install react-lazyload"
- name: "recharts"
version: "^2.0.0"
purpose: "Alternative charting library for custom visualizations"
install: "npm install recharts"
validation_checks:
- "All widgets have loading, error, and success states"
- "KPI cards display value, trend, and comparison"
- "Filter context coordinates state across widgets"
- "Real-time updates implemented (SSE, WebSocket, or polling)"
- "Lazy loading used for performance optimization"
- "Widget-level caching implemented with TTL"
- "Responsive breakpoints defined (desktop, tablet, mobile)"
- "Dashboard templates available for quick setup"
- "Export functionality works (PDF, image, data)"
- "Dashboard layout persists to localStorage (if customizable)"
anti_patterns:
- name: "Fetching all widget data sequentially"
avoid: "await fetchKPIs(); await fetchCharts(); await fetchTables();"
use: "Promise.all([fetchKPIs(), fetchCharts(), fetchTables()])"
- name: "No loading states for widgets"
avoid: "Widgets show nothing while loading"
use: "Skeleton loaders or spinners for each widget"
- name: "Independent filter state per widget"
avoid: "Each widget manages its own filters"
use: "Global filter context coordinating all widgets"
- name: "Continuous polling without visibility detection"
avoid: "setInterval runs even when tab is hidden"
use: "Smart polling that pauses when document.hidden is true"
- name: "No widget error boundaries"
avoid: "One widget error crashes entire dashboard"
use: "Error boundaries wrapping each widget independently"
- name: "Hardcoded widget layouts"
avoid: "Fixed positions that don't adapt to screen size"
use: "Responsive grid with breakpoints or user customization"
- name: "Fetching same data repeatedly"
avoid: "Re-fetching identical data on every render"
use: "Widget-level caching with TTL expiration"
- name: "No export functionality"
avoid: "Users can't export dashboard insights"
use: "Export to PDF, image, or CSV for sharing"
KPI Card Design Patterns
Table of Contents
- Essential Elements
- Number Formatting
- Trend Indicators
- Sparkline Integration
- Card Variations
- Interactive Features
- Accessibility
Essential Elements
Basic KPI Card Structure
interface KPICard {
label: string; // "Monthly Revenue"
value: number; // 1245832
unit?: string; // "$", "%", "users"
trend?: {
direction: 'up' | 'down' | 'neutral';
value: number; // 15.3
comparison: string; // "vs last month"
};
sparkline?: number[]; // [100, 120, 115, 140, 155]
status?: 'success' | 'warning' | 'error' | 'neutral';
icon?: ReactNode;
onClick?: () => void;
}Visual Hierarchy
┌──────────────────────────────┐
│ 👥 Active Users [ℹ️] [⚙️] │ ← Icon, Label, Actions
│ │
│ 12,543 │ ← Primary Value (largest)
│ │
│ ↑ 234 (+1.9%) │ ← Change (medium)
│ vs yesterday │ ← Comparison (small)
│ │
│ ▂▃▅▄▆▇█▆▅ Last 7 days │ ← Sparkline + Period
└──────────────────────────────┘Number Formatting
Large Number Abbreviation
function formatLargeNumber(value: number): string {
const abbreviations = [
{ threshold: 1e9, suffix: 'B' },
{ threshold: 1e6, suffix: 'M' },
{ threshold: 1e3, suffix: 'K' }
];
for (const { threshold, suffix } of abbreviations) {
if (Math.abs(value) >= threshold) {
const formatted = (value / threshold).toFixed(1);
return `${formatted}${suffix}`;
}
}
return value.toLocaleString();
}
// Examples:
// 1234567 → "1.2M"
// 45678 → "45.7K"
// 999 → "999"Currency Formatting
function formatCurrency(value: number, currency = 'USD'): string {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 0,
notation: value > 1e6 ? 'compact' : 'standard'
});
return formatter.format(value);
}
// Examples:
// 1234567 → "$1.2M"
// 45678 → "$45,678"
// 999.99 → "$1,000"Percentage Formatting
function formatPercentage(value: number, decimals = 1): string {
const formatted = value.toFixed(decimals);
const sign = value > 0 ? '+' : '';
return `${sign}${formatted}%`;
}
// Examples:
// 15.345 → "+15.3%"
// -2.1 → "-2.1%"
// 0 → "0.0%"Trend Indicators
Direction Arrows
function TrendArrow({ direction, value }: TrendProps) {
const arrows = {
up: '↑',
down: '↓',
neutral: '→'
};
const colors = {
up: 'text-green-600',
down: 'text-red-600',
neutral: 'text-gray-500'
};
return (
<span className={`flex items-center ${colors[direction]}`}>
<span className="text-lg">{arrows[direction]}</span>
<span className="ml-1">{formatPercentage(value)}</span>
</span>
);
}Status Badges
function StatusBadge({ deltaType, value, label }: BadgeProps) {
const styles = {
increase: 'bg-green-100 text-green-800',
decrease: 'bg-red-100 text-red-800',
neutral: 'bg-gray-100 text-gray-800'
};
return (
<div className={`inline-flex items-center px-2 py-1 rounded ${styles[deltaType]}`}>
<TrendArrow direction={deltaType} />
<span className="ml-1">{value}</span>
{label && <span className="ml-2 text-sm">{label}</span>}
</div>
);
}Sparkline Integration
Basic Sparkline
import { Sparklines, SparklinesLine, SparklinesSpots } from 'react-sparklines';
function KPISparkline({ data, color = '#10b981' }) {
return (
<Sparklines data={data} width={100} height={30} margin={2}>
<SparklinesLine
style={{
strokeWidth: 2,
stroke: color,
fill: 'none'
}}
/>
<SparklinesSpots
size={2}
style={{ fill: color }}
/>
</Sparklines>
);
}Inline SVG Sparkline (No Library)
function MiniSparkline({ data, width = 100, height = 30 }) {
const max = Math.max(...data);
const min = Math.min(...data);
const range = max - min || 1;
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * width;
const y = height - ((value - min) / range) * height;
return `${x},${y}`;
}).join(' ');
return (
<svg width={width} height={height} className="sparkline">
<polyline
points={points}
fill="none"
stroke="currentColor"
strokeWidth="2"
/>
</svg>
);
}Card Variations
Compact KPI Card
function CompactKPI({ label, value, trend }) {
return (
<div className="flex items-center justify-between p-3">
<div>
<p className="text-xs text-gray-600">{label}</p>
<p className="text-lg font-semibold">{value}</p>
</div>
{trend && (
<div className="text-right">
<TrendArrow {...trend} />
</div>
)}
</div>
);
}Detailed KPI Card
function DetailedKPI({
label,
value,
trend,
sparkline,
breakdown,
actions
}) {
return (
<Card className="p-4">
{/* Header */}
<div className="flex justify-between mb-3">
<h3 className="text-sm font-medium text-gray-600">{label}</h3>
<div className="flex gap-2">
{actions?.map(action => (
<IconButton key={action.id} {...action} />
))}
</div>
</div>
{/* Main Value */}
<div className="mb-3">
<p className="text-3xl font-bold">{value}</p>
{trend && <TrendIndicator {...trend} />}
</div>
{/* Sparkline */}
{sparkline && (
<div className="mb-3">
<MiniSparkline data={sparkline} />
</div>
)}
{/* Breakdown */}
{breakdown && (
<div className="border-t pt-3">
{breakdown.map(item => (
<div key={item.label} className="flex justify-between text-sm">
<span className="text-gray-600">{item.label}</span>
<span className="font-medium">{item.value}</span>
</div>
))}
</div>
)}
</Card>
);
}Goal-Based KPI Card
function GoalKPI({ label, current, target, unit = '' }) {
const percentage = (current / target) * 100;
const status = percentage >= 100 ? 'success' :
percentage >= 75 ? 'warning' : 'error';
return (
<Card className="p-4">
<h3 className="text-sm text-gray-600 mb-2">{label}</h3>
<div className="flex items-baseline gap-2 mb-3">
<span className="text-2xl font-bold">
{unit}{current.toLocaleString()}
</span>
<span className="text-sm text-gray-500">
of {unit}{target.toLocaleString()}
</span>
</div>
<ProgressBar value={percentage} status={status} />
<p className="text-sm mt-2">
{percentage.toFixed(1)}% of goal achieved
</p>
</Card>
);
}Interactive Features
Clickable KPI with Drill-Down
function InteractiveKPI({ data, onDrillDown }) {
const [isHovered, setIsHovered] = useState(false);
return (
<Card
className="p-4 cursor-pointer transition-shadow"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={() => onDrillDown(data.id)}
style={{
boxShadow: isHovered ? 'var(--shadow-lg)' : 'var(--shadow-md)'
}}
>
<div className="flex justify-between items-start">
<div>
<p className="text-sm text-gray-600">{data.label}</p>
<p className="text-2xl font-bold mt-1">{data.value}</p>
</div>
{isHovered && (
<ChevronRight className="text-gray-400" />
)}
</div>
{data.trend && (
<div className="mt-3">
<TrendIndicator {...data.trend} />
</div>
)}
</Card>
);
}Real-Time Updating KPI
function LiveKPI({ endpoint, label }) {
const [data, setData] = useState(null);
const [isUpdating, setIsUpdating] = useState(false);
useEffect(() => {
const eventSource = new EventSource(endpoint);
eventSource.onmessage = (event) => {
setIsUpdating(true);
const newData = JSON.parse(event.data);
setTimeout(() => {
setData(newData);
setIsUpdating(false);
}, 300);
};
return () => eventSource.close();
}, [endpoint]);
return (
<Card className={`p-4 ${isUpdating ? 'animate-pulse' : ''}`}>
<p className="text-sm text-gray-600">{label}</p>
<p className="text-2xl font-bold mt-1">
{data?.value || '---'}
</p>
{data?.lastUpdated && (
<p className="text-xs text-gray-400 mt-2">
Updated {formatRelativeTime(data.lastUpdated)}
</p>
)}
</Card>
);
}Accessibility
ARIA Labels and Roles
function AccessibleKPI({ label, value, trend }) {
const trendDescription = trend
? `${trend.direction} ${trend.value}% ${trend.comparison}`
: '';
return (
<div
role="region"
aria-label={`${label} metric`}
className="kpi-card"
>
<h3 id={`kpi-${label}-title`}>{label}</h3>
<div
aria-labelledby={`kpi-${label}-title`}
aria-describedby={`kpi-${label}-trend`}
>
<span className="sr-only">Current value:</span>
<span className="text-2xl font-bold">{value}</span>
</div>
{trend && (
<div id={`kpi-${label}-trend`}>
<span className="sr-only">Trend:</span>
<span aria-label={trendDescription}>
<TrendArrow {...trend} />
</span>
</div>
)}
</div>
);
}Keyboard Navigation
function KeyboardNavigableKPI({ data, onSelect }) {
return (
<div
tabIndex={0}
role="button"
aria-pressed={false}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onSelect(data);
}
}}
onClick={() => onSelect(data)}
className="kpi-card focus:ring-2 focus:ring-blue-500"
>
{/* KPI content */}
</div>
);
}Complete KPI Card Implementation
function KPICard({
label,
value,
unit = '',
trend,
sparkline,
status = 'neutral',
icon,
onClick,
className = ''
}) {
const formattedValue = typeof value === 'number'
? formatLargeNumber(value)
: value;
const statusColors = {
success: 'border-l-4 border-green-500',
warning: 'border-l-4 border-yellow-500',
error: 'border-l-4 border-red-500',
neutral: ''
};
return (
<Card
className={`p-4 ${statusColors[status]} ${onClick ? 'cursor-pointer' : ''} ${className}`}
onClick={onClick}
>
{/* Header */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
{icon && <span className="text-gray-500">{icon}</span>}
<h3 className="text-sm font-medium text-gray-600">{label}</h3>
</div>
</div>
{/* Value */}
<div className="mb-3">
<p className="text-2xl font-bold">
{unit && <span className="text-lg">{unit}</span>}
{formattedValue}
</p>
</div>
{/* Trend */}
{trend && (
<div className="flex items-center gap-2 mb-3">
<TrendArrow direction={trend.direction} />
<span className="text-sm font-medium">
{formatPercentage(trend.value)}
</span>
<span className="text-sm text-gray-500">
{trend.comparison}
</span>
</div>
)}
{/* Sparkline */}
{sparkline && sparkline.length > 0 && (
<div className="mt-3">
<MiniSparkline data={sparkline} />
</div>
)}
</Card>
);
}#!/usr/bin/env python3
"""
Export Dashboard Data
Export dashboard data to CSV, JSON, or Excel for offline analysis or reporting.
Usage:
python scripts/export-dashboard.py --format csv --output report.csv
python scripts/export-dashboard.py --format excel --output report.xlsx --include-charts
"""
import argparse
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path
def generate_sample_data():
"""Generate sample dashboard data"""
data = {
"kpis": {
"revenue": 63000,
"users": 12543,
"conversion_rate": 3.8,
"growth_rate": 18.2,
},
"timeseries": [
{"date": "2025-07-01", "revenue": 42000, "users": 9800},
{"date": "2025-08-01", "revenue": 45000, "users": 10200},
{"date": "2025-09-01", "revenue": 51000, "users": 11100},
{"date": "2025-10-01", "revenue": 49000, "users": 10800},
{"date": "2025-11-01", "revenue": 58000, "users": 12000},
{"date": "2025-12-01", "revenue": 63000, "users": 12543},
],
"products": [
{"product": "Pro Plan", "sales": 125000, "units": 450},
{"product": "Enterprise", "sales": 98000, "units": 120},
{"product": "Starter", "sales": 67000, "units": 890},
{"product": "Add-ons", "sales": 34000, "units": 1200},
],
"regions": [
{"region": "North America", "revenue": 280000, "users": 5200},
{"region": "Europe", "revenue": 180000, "users": 3800},
{"region": "Asia Pacific", "revenue": 120000, "users": 3543},
],
}
return data
def export_to_csv(data, output_path):
"""Export to CSV format"""
import csv
with open(output_path, 'w', newline='') as f:
# Write KPIs section
writer = csv.writer(f)
writer.writerow(["Dashboard Export", datetime.now().isoformat()])
writer.writerow([])
writer.writerow(["KPIs"])
writer.writerow(["Metric", "Value"])
for key, value in data["kpis"].items():
writer.writerow([key.replace('_', ' ').title(), value])
writer.writerow([])
writer.writerow(["Timeseries Data"])
if data["timeseries"]:
headers = list(data["timeseries"][0].keys())
writer.writerow(headers)
for row in data["timeseries"]:
writer.writerow([row[h] for h in headers])
writer.writerow([])
writer.writerow(["Product Data"])
if data["products"]:
headers = list(data["products"][0].keys())
writer.writerow(headers)
for row in data["products"]:
writer.writerow([row[h] for h in headers])
print(f"✓ Exported to CSV: {output_path}")
def export_to_json(data, output_path):
"""Export to JSON format"""
export_data = {
"exported_at": datetime.now().isoformat(),
"dashboard_data": data,
}
with open(output_path, 'w') as f:
json.dump(export_data, f, indent=2)
print(f"✓ Exported to JSON: {output_path}")
def export_to_excel(data, output_path, include_charts=False):
"""Export to Excel with multiple sheets"""
try:
import pandas as pd
except ImportError:
print("Error: pandas required for Excel export")
print("Install: pip install pandas openpyxl")
sys.exit(1)
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# KPIs sheet
kpi_df = pd.DataFrame([
{"Metric": k.replace('_', ' ').title(), "Value": v}
for k, v in data["kpis"].items()
])
kpi_df.to_excel(writer, sheet_name='KPIs', index=False)
# Timeseries sheet
ts_df = pd.DataFrame(data["timeseries"])
ts_df.to_excel(writer, sheet_name='Timeseries', index=False)
# Products sheet
prod_df = pd.DataFrame(data["products"])
prod_df.to_excel(writer, sheet_name='Products', index=False)
# Regions sheet
region_df = pd.DataFrame(data["regions"])
region_df.to_excel(writer, sheet_name='Regions', index=False)
if include_charts:
# Add charts to Excel (requires openpyxl)
from openpyxl.chart import LineChart, Reference
workbook = writer.book
ts_sheet = workbook['Timeseries']
# Create revenue trend chart
chart = LineChart()
chart.title = "Revenue Trend"
chart.x_axis.title = "Date"
chart.y_axis.title = "Revenue ($)"
data_ref = Reference(ts_sheet, min_col=2, min_row=1, max_row=len(data["timeseries"]) + 1)
cats_ref = Reference(ts_sheet, min_col=1, min_row=2, max_row=len(data["timeseries"]) + 1)
chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(cats_ref)
ts_sheet.add_chart(chart, "E2")
print(f"✓ Exported to Excel: {output_path}")
if include_charts:
print(" (with embedded charts)")
def main():
parser = argparse.ArgumentParser(description="Export dashboard data")
parser.add_argument(
"--format",
choices=["csv", "json", "excel"],
default="json",
help="Export format"
)
parser.add_argument(
"--output",
required=True,
help="Output file path"
)
parser.add_argument(
"--include-charts",
action="store_true",
help="Include charts in Excel export"
)
parser.add_argument(
"--api-url",
help="Fetch live data from API endpoint"
)
args = parser.parse_args()
# Generate or fetch data
if args.api_url:
import requests
try:
response = requests.get(args.api_url)
response.raise_for_status()
data = response.json()
except Exception as e:
print(f"Error fetching data from API: {e}", file=sys.stderr)
sys.exit(1)
else:
print("Using sample data (use --api-url to fetch live data)")
data = generate_sample_data()
# Export based on format
try:
if args.format == "csv":
export_to_csv(data, args.output)
elif args.format == "json":
export_to_json(data, args.output)
elif args.format == "excel":
export_to_excel(data, args.output, args.include_charts)
print(f"\n✓ Export complete!")
print(f" Format: {args.format.upper()}")
print(f" File: {args.output}")
except Exception as e:
print(f"Error during export: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Related skills
How it compares
Pick creating-dashboards over generic chart skills when you need full dashboard architecture with coordinated filters, KPI cards, and layout persistence rather than a single visualization component.
FAQ
Which libraries does it use?
Tremor for quick analytics dashboards and react-grid-layout for customizable drag-and-drop layouts.
How does it handle real-time updates?
It recommends server-sent events (EventSource) to stream widget updates into the dashboard.