create-bluecopa-react-app 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/templates/latest/AGENT.md +13 -15
  3. package/templates/latest/README.md +2 -2
  4. package/templates/latest/clean.sh +1 -0
  5. package/templates/latest/setup.sh +5 -4
  6. package/templates/latest/src/App.tsx +11 -7
  7. package/templates/latest/src/components/charts/AreaChart.tsx +80 -0
  8. package/templates/latest/src/components/charts/DonutChart.tsx +73 -0
  9. package/templates/latest/src/components/charts/SparkAreaChart.tsx +52 -0
  10. package/templates/latest/src/components/layout/dashboard-header.tsx +1 -1
  11. package/templates/latest/src/components/layout/dashboard-layout.tsx +19 -11
  12. package/templates/latest/src/components/{page → layout}/navbar.tsx +2 -0
  13. package/templates/latest/src/components/layout/sidebar.tsx +2 -1
  14. package/templates/latest/src/components/page/dashboard/DashboardMetrics.tsx +97 -0
  15. package/templates/latest/src/components/page/dashboard/PaymentMethodsAnalysis.tsx +182 -0
  16. package/templates/latest/src/components/page/dashboard/RevenueAnalytics.tsx +505 -0
  17. package/templates/latest/src/components/page/dashboard/SalesAnalytics.tsx +313 -0
  18. package/templates/latest/src/components/page/dashboard/TransactionsTable.tsx +256 -0
  19. package/templates/latest/src/components/page/dashboard/dashboard-utils.ts +147 -0
  20. package/templates/latest/src/components/page/dashboard/dashboard.tsx +185 -0
  21. package/templates/latest/src/components/ui/bluecopa-logo.tsx +3 -0
  22. package/templates/latest/src/components/ui/label.tsx +0 -2
  23. package/templates/latest/src/components/ui/select.tsx +0 -2
  24. package/templates/latest/src/pages/Dashboard.tsx +1 -1
  25. package/templates/latest/src/single-spa.tsx +38 -28
  26. package/templates/latest/tailwind.config.js +1 -2
  27. package/templates/latest/tsconfig.app.json +6 -0
  28. package/templates/latest/tsconfig.json +6 -6
  29. package/templates/latest/tsconfig.node.json +5 -1
  30. package/templates/latest/vite.config.ts +1 -1
  31. package/templates/latest/src/components/page/dashboard.tsx +0 -1506
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bluecopa-react-app",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "CLI tool to create bluecopa React applications",
5
5
  "main": "./bin/create-bluecopa-react-app.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@ This template is designed to work seamlessly with AI coding assistants for rapid
13
13
  - **Build Tool**: Vite (fast HMR and building)
14
14
  - **Styling**: TailwindCSS with BlueCopa design system
15
15
  - **UI Components**: Radix UI primitives (add via shadcn-ui for pre-styled components)
16
- - **Data Fetching**: TanStack Query with @bluecopa/core hooks
16
+ - **Data Fetching**: TanStack Query with @bluecopa/react hooks
17
17
  - **Charts**: Recharts for data visualization
18
18
  - **Icons**: Lucide React
19
19
 
@@ -22,11 +22,9 @@ This template is designed to work seamlessly with AI coding assistants for rapid
22
22
  src/
23
23
  ├── components/ui/ # Reusable UI components (e.g., Button, Card via shadcn-ui; bluecopa-logo)
24
24
  ├── components/ # Business logic components (Navbar, DashboardLayout, etc.)
25
- ├── hooks/ # Custom React hooks (if needed; primarily use @bluecopa/core)
26
- ├── pages/ # Route components (Home, Dashboard)
27
- ├── providers/ # React context providers (e.g., QueryProvider)
28
- ├── lib/ # Utility functions and helpers (e.g., utils.ts with cn)
29
- └── types/ # TypeScript type definitions (e.g., for API responses)
25
+ ├── hooks/ # Custom React hooks (if needed; primarily use @bluecopa/react)
26
+ ├── lib/ # Utility functions and helpers (e.g., utils.ts with cn)
27
+ └── types/ # TypeScript type definitions (e.g., for API responses)
30
28
  ```
31
29
 
32
30
  ## 🔧 Development Patterns
@@ -38,7 +36,7 @@ When asked to create a new page:
38
36
  1. **Create the page component** in `src/pages/NewPage.tsx`:
39
37
  ```tsx
40
38
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
41
- import { useGetWorkflowInstanceStatusById } from '@bluecopa/core';
39
+ import { useGetWorkflowInstanceStatusById } from '@bluecopa/react';
42
40
 
43
41
  export default function NewPage() {
44
42
  const { data: workflowStatus, isLoading } = useGetWorkflowInstanceStatusById('workflowId');
@@ -54,7 +52,7 @@ export default function NewPage() {
54
52
  <CardContent>
55
53
  {/* Page content */}
56
54
  <p>Workflow status: {workflowStatus?.status}</p>
57
- </CardContent>
55
+ CardContent>
58
56
  </Card>
59
57
  </div>
60
58
  );
@@ -69,7 +67,7 @@ import NewPage from '@/pages/NewPage';
69
67
  <Route path="/new-page" element={<NewPage />} />
70
68
  ```
71
69
 
72
- 3. **Update navigation** in `src/components/page/navbar.tsx` (import icon from lucide-react):
70
+ 3. **Update navigation** in `src/components/layout/navbar.tsx` (import icon from lucide-react):
73
71
  ```tsx
74
72
  import { Settings } from 'lucide-react';
75
73
 
@@ -81,9 +79,9 @@ const navigation = [
81
79
 
82
80
  ### 2. Using BlueCopa API Hooks
83
81
 
84
- Install @bluecopa/core if not present: `npm install @bluecopa/core`
82
+ Install @bluecopa/react if not present: `npm install @bluecopa/react`
85
83
 
86
- Use the pre-configured hooks from `@bluecopa/core`:
84
+ Use the pre-configured hooks from `@bluecopa/react`:
87
85
  ```tsx
88
86
  import { useGetFileUrlByFileId, useGetTableById, useGetWorkflowInstanceStatusById } from '@bluecopa/core';
89
87
 
@@ -201,7 +199,7 @@ function ChartComponent({ data }: { data: ChartData[] }) {
201
199
  1. Create component in `src/components/` (e.g., NewCard.tsx)
202
200
  2. Use Card component wrapper from ui/
203
201
  3. Include loading and error states with TanStack Query
204
- 4. Use @bluecopa/core hooks for data (e.g., useGetTableById)
202
+ 4. Use @bluecopa/react hooks for data (e.g., useGetTableById)
205
203
 
206
204
  ### Creating Forms
207
205
  1. Use Radix UI form components (add via shadcn-ui: `npx shadcn-ui@latest add form`)
@@ -213,7 +211,7 @@ function ChartComponent({ data }: { data: ChartData[] }) {
213
211
  1. For complex tables, install and use AG Grid (@bluecopa/aggrid)
214
212
  2. For simple tables, use HTML table with Tailwind styling
215
213
  3. Include sorting, filtering, and pagination as needed
216
- 4. Integrate with @bluecopa/core for data
214
+ 4. Integrate with @bluecopa/react for data
217
215
 
218
216
  ### Implementing Search
219
217
  1. Add search state management with React state or form libraries
@@ -241,7 +239,7 @@ import { cn } from '@/lib/utils';
241
239
  ### TypeScript
242
240
  Always include proper TypeScript types:
243
241
  ```tsx
244
- import { FileUrlResponse, TableData, WorkflowStatus } from '@/types';
242
+ import { FileUrlResponse, TableData, WorkflowStatus } from '@bluecopa/core/types';
245
243
 
246
244
  interface ComponentProps {
247
245
  data: TableData[];
@@ -269,7 +267,7 @@ if (!data) return <div className="p-4 text-gray-500">No data available</div>;
269
267
  - [Recharts Documentation](https://recharts.org)
270
268
  - [TanStack Query Documentation](https://tanstack.com/query/latest)
271
269
  - [Lucide React Icons](https://lucide.dev)
272
- - [BlueCopa Core Package](https://www.npmjs.com/package/@bluecopa/core)
270
+ - [BlueCopa React Package](https://www.npmjs.com/package/@bluecopa/react)
273
271
 
274
272
  ## 🔄 Development Workflow
275
273
 
@@ -103,7 +103,7 @@ npx create-bluecopa-react-app my-dashboard
103
103
  src/
104
104
  ├── components/ # Reusable components
105
105
  │ ├── layout/ # Layout components (dashboard-header.tsx, dashboard-layout.tsx, sidebar.tsx)
106
- │ ├── page/ # Page-specific components (dashboard.tsx, navbar.tsx)
106
+ │ ├── page/ # Page-specific components (dashboard.tsx)
107
107
  │ ├── tables/ # Table components (data-grid.tsx)
108
108
  │ └── ui/ # Base UI components (alert.tsx, avatar.tsx, badge.tsx, bluecopa-logo.tsx, button.tsx, card.tsx, dropdown-menu.tsx, input.tsx, label.tsx, select.tsx)
109
109
  ├── hooks/ # Custom React hooks
@@ -172,7 +172,7 @@ export default function App() {
172
172
  ```
173
173
 
174
174
  ### Adding Navigation Links
175
- Update the navigation in `src/components/page/navbar.tsx`:
175
+ Update the navigation in `src/components/layout/navbar.tsx`:
176
176
 
177
177
  ```tsx
178
178
  const navigation = [
@@ -12,6 +12,7 @@ echo "✅ Removed dist directory"
12
12
  rm -rf node_modules/
13
13
  rm -f package-lock.json
14
14
  rm -f yarn.lock
15
+ rm -f pnpm-lock.yaml
15
16
  echo "✅ Removed node_modules and lock files"
16
17
 
17
18
  # Remove environment files (keeping example)
@@ -42,10 +42,11 @@ else
42
42
  fi
43
43
 
44
44
  echo "🎨 Setting up Tailwind CSS..."
45
- npx tailwindcss init -p 2>/dev/null || echo "✅ Tailwind already configured"
46
-
47
- echo "🔍 Running type check..."
48
- npm run type-check
45
+ if [ ! -f tailwind.config.js ] && [ ! -f tailwind.config.cjs ]; then
46
+ npx tailwindcss init -p
47
+ else
48
+ echo "✅ Tailwind already configured"
49
+ fi
49
50
 
50
51
  echo "✅ Setup complete!"
51
52
  echo ""
@@ -1,15 +1,19 @@
1
- import { Routes, Route } from 'react-router-dom'
1
+ import { Routes, Route, Navigate } from 'react-router-dom'
2
+ import { Suspense, lazy } from 'react'
2
3
  import QueryProvider from '@/providers/query-provider'
3
4
  import Home from '@/pages/Home'
4
- import Dashboard from '@/pages/Dashboard'
5
+ const Dashboard = lazy(() => import('@/pages/Dashboard'))
5
6
 
6
7
  export default function App() {
7
8
  return (
8
9
  <QueryProvider>
9
- <Routes>
10
- <Route path="/" element={<Home />} />
11
- <Route path="/dashboard" element={<Dashboard />} />
12
- </Routes>
10
+ <Suspense fallback={<div className="p-4 text-sm text-muted-foreground">Loading…</div>}>
11
+ <Routes>
12
+ <Route path="/" element={<Home />} />
13
+ <Route path="/dashboard" element={<Dashboard />} />
14
+ <Route path="*" element={<Navigate to="/" replace />} />
15
+ </Routes>
16
+ </Suspense>
13
17
  </QueryProvider>
14
18
  )
15
- }
19
+ }
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import {
3
+ ResponsiveContainer,
4
+ AreaChart as RechartsAreaChart,
5
+ Area,
6
+ XAxis,
7
+ YAxis,
8
+ CartesianGrid,
9
+ Tooltip,
10
+ Legend
11
+ } from 'recharts';
12
+
13
+ interface AreaChartProps {
14
+ data: any[];
15
+ index: string;
16
+ categories: string[];
17
+ colors?: string[];
18
+ className?: string;
19
+ valueFormatter?: (value: any) => any;
20
+ showLegend?: boolean;
21
+ showGridLines?: boolean;
22
+ showXAxis?: boolean;
23
+ showYAxis?: boolean;
24
+ yAxisWidth?: number;
25
+ }
26
+
27
+ export const AreaChart: React.FC<AreaChartProps> = ({
28
+ data,
29
+ index,
30
+ categories,
31
+ colors = ['#3B82F6'],
32
+ className = "h-80",
33
+ valueFormatter,
34
+ showLegend = true,
35
+ showGridLines = true,
36
+ showXAxis = true,
37
+ showYAxis = true,
38
+ yAxisWidth
39
+ }) => {
40
+ const colorMap: { [key: string]: string } = {
41
+ blue: '#3B82F6',
42
+ emerald: '#10B981',
43
+ violet: '#8B5CF6',
44
+ rose: '#F43F5E',
45
+ amber: '#F59E0B',
46
+ cyan: '#06B6D4',
47
+ slate: '#64748B',
48
+ indigo: '#6366F1'
49
+ };
50
+
51
+ const getColor = (color: string, index: number) => {
52
+ return colorMap[color] || colors[index] || '#3B82F6';
53
+ };
54
+
55
+ return (
56
+ <div className={className}>
57
+ <ResponsiveContainer width="100%" height="100%">
58
+ <RechartsAreaChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
59
+ {showGridLines && <CartesianGrid strokeDasharray="3 3" />}
60
+ {showXAxis && <XAxis dataKey={index} />}
61
+ {showYAxis && <YAxis width={yAxisWidth || 60} />}
62
+ <Tooltip
63
+ formatter={(value: any) => valueFormatter ? valueFormatter(value) : value}
64
+ />
65
+ {showLegend && <Legend />}
66
+ {categories?.map((category: string, idx: number) => (
67
+ <Area
68
+ key={category}
69
+ type="monotone"
70
+ dataKey={category}
71
+ stroke={getColor(colors[idx], idx)}
72
+ fill={getColor(colors[idx], idx)}
73
+ fillOpacity={0.3}
74
+ />
75
+ ))}
76
+ </RechartsAreaChart>
77
+ </ResponsiveContainer>
78
+ </div>
79
+ );
80
+ };
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import {
3
+ ResponsiveContainer,
4
+ PieChart,
5
+ Pie,
6
+ Cell,
7
+ Tooltip,
8
+ Legend
9
+ } from 'recharts';
10
+
11
+ interface DonutChartProps {
12
+ data: any[];
13
+ category: string;
14
+ index: string;
15
+ colors?: string[];
16
+ className?: string;
17
+ valueFormatter?: (value: any) => any;
18
+ variant?: "donut" | "pie";
19
+ }
20
+
21
+ export const DonutChart: React.FC<DonutChartProps> = ({
22
+ data,
23
+ category,
24
+ index,
25
+ colors = ['#3B82F6'],
26
+ className = "h-64",
27
+ valueFormatter,
28
+ variant = "donut"
29
+ }) => {
30
+ const colorMap: { [key: string]: string } = {
31
+ blue: '#3B82F6',
32
+ emerald: '#10B981',
33
+ violet: '#8B5CF6',
34
+ rose: '#F43F5E',
35
+ amber: '#F59E0B',
36
+ cyan: '#06B6D4',
37
+ slate: '#64748B',
38
+ indigo: '#6366F1'
39
+ };
40
+
41
+ const getColor = (color: string, index: number) => {
42
+ return colorMap[color] || colors[index] || '#3B82F6';
43
+ };
44
+
45
+ const COLORS = colors.map((color: string, idx: number) => getColor(color, idx));
46
+
47
+ return (
48
+ <div className={className}>
49
+ <ResponsiveContainer width="100%" height="100%">
50
+ <PieChart>
51
+ <Pie
52
+ data={data}
53
+ cx="50%"
54
+ cy="50%"
55
+ innerRadius={variant === "donut" ? 60 : 0}
56
+ outerRadius={80}
57
+ paddingAngle={5}
58
+ dataKey={category}
59
+ nameKey={index}
60
+ >
61
+ {data.map((_entry: any, index: number) => (
62
+ <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
63
+ ))}
64
+ </Pie>
65
+ <Tooltip
66
+ formatter={(value: any) => valueFormatter ? valueFormatter(value) : value}
67
+ />
68
+ <Legend />
69
+ </PieChart>
70
+ </ResponsiveContainer>
71
+ </div>
72
+ );
73
+ };
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import {
3
+ ResponsiveContainer,
4
+ AreaChart as RechartsAreaChart,
5
+ Area
6
+ } from 'recharts';
7
+
8
+ interface SparkAreaChartProps {
9
+ data: any[];
10
+ categories: string[];
11
+ colors?: string[];
12
+ className?: string;
13
+ }
14
+
15
+ export const SparkAreaChart: React.FC<SparkAreaChartProps> = ({
16
+ data,
17
+ categories,
18
+ colors = ['#3B82F6'],
19
+ className = "h-10"
20
+ }) => {
21
+ const colorMap: { [key: string]: string } = {
22
+ blue: '#3B82F6',
23
+ emerald: '#10B981',
24
+ violet: '#8B5CF6',
25
+ rose: '#F43F5E',
26
+ amber: '#F59E0B',
27
+ cyan: '#06B6D4'
28
+ };
29
+
30
+ const getColor = (color: string, index: number) => {
31
+ return colorMap[color] || colors[index] || '#3B82F6';
32
+ };
33
+
34
+ return (
35
+ <div className={className}>
36
+ <ResponsiveContainer width="100%" height="100%">
37
+ <RechartsAreaChart data={data} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
38
+ {categories?.map((category: string, idx: number) => (
39
+ <Area
40
+ key={category}
41
+ type="monotone"
42
+ dataKey={category}
43
+ stroke={getColor(colors[idx], idx)}
44
+ fill={getColor(colors[idx], idx)}
45
+ fillOpacity={0.6}
46
+ />
47
+ ))}
48
+ </RechartsAreaChart>
49
+ </ResponsiveContainer>
50
+ </div>
51
+ );
52
+ };
@@ -87,7 +87,7 @@ export default function DashboardHeader({ title, subtitle }: DashboardHeaderProp
87
87
  </div>
88
88
 
89
89
  {/* Notifications */}
90
- <Button variant="ghost" size="sm" className="relative">
90
+ <Button variant="ghost" size="sm" className="relative" aria-label="Open notifications, 3 unread">
91
91
  <Bell className="h-5 w-5" />
92
92
  <span className="absolute -top-1 -right-1 h-4 w-4 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
93
93
  3
@@ -1,29 +1,37 @@
1
- import { ReactNode } from 'react'
2
- import Sidebar from './sidebar'
3
- import DashboardHeader from './dashboard-header'
1
+ import { ReactNode } from "react";
2
+ import Sidebar from "./sidebar";
3
+ import DashboardHeader from "./dashboard-header";
4
4
 
5
5
  interface DashboardLayoutProps {
6
- children: ReactNode
7
- title?: string
8
- subtitle?: string
6
+ children: ReactNode;
7
+ title?: string;
8
+ subtitle?: string;
9
9
  }
10
10
 
11
- export default function DashboardLayout({ children, title, subtitle }: DashboardLayoutProps) {
11
+ export default function DashboardLayout({
12
+ children,
13
+ title,
14
+ subtitle,
15
+ }: DashboardLayoutProps) {
12
16
  return (
13
17
  <div className="flex h-screen bg-gray-50">
14
18
  {/* Sidebar */}
15
19
  <Sidebar />
16
-
20
+
17
21
  {/* Main content area */}
18
22
  <div className="flex-1 flex flex-col overflow-hidden">
19
23
  {/* Header */}
20
24
  <DashboardHeader title={title} subtitle={subtitle} />
21
-
25
+
22
26
  {/* Main content */}
23
- <main className="flex-1 overflow-y-auto p-6">
27
+ <main
28
+ id="main-content"
29
+ role="main"
30
+ className="flex-1 overflow-y-auto p-6"
31
+ >
24
32
  {children}
25
33
  </main>
26
34
  </div>
27
35
  </div>
28
- )
36
+ );
29
37
  }
@@ -60,6 +60,8 @@ export default function Navbar() {
60
60
  type="button"
61
61
  className="inline-flex items-center justify-center p-2 rounded-md text-blue-200 hover:text-white hover:bg-blue-700"
62
62
  onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
63
+ aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
64
+ aria-expanded={mobileMenuOpen}
63
65
  >
64
66
  {mobileMenuOpen ? (
65
67
  <X className="h-6 w-6" />
@@ -22,7 +22,7 @@ export default function Sidebar() {
22
22
  </div>
23
23
 
24
24
  {/* Navigation */}
25
- <nav className="flex-1 space-y-1 px-3 py-4">
25
+ <nav className="flex-1 space-y-1 px-3 py-4" aria-label="Main navigation">
26
26
  {navigation.map((item) => {
27
27
  const isActive = location.pathname === item.href;
28
28
  return (
@@ -35,6 +35,7 @@ export default function Sidebar() {
35
35
  ? "bg-blue-50 text-blue-700 border-r-2 border-blue-600"
36
36
  : "text-gray-700 hover:bg-gray-50 hover:text-gray-900"
37
37
  )}
38
+ aria-current={isActive ? "page" : undefined}
38
39
  >
39
40
  <item.icon
40
41
  className={cn(
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import { Card, CardContent, CardTitle } from '@/components/ui/card';
3
+ import { ArrowUpRight, ArrowDownRight } from 'lucide-react';
4
+ import { SparkAreaChart } from '@/components/charts/SparkAreaChart';
5
+
6
+ interface Metric {
7
+ title: string;
8
+ value: string;
9
+ change: string;
10
+ trend: 'up' | 'down';
11
+ icon: string;
12
+ color: string;
13
+ }
14
+
15
+ interface DashboardMetricsProps {
16
+ ecommerceMetrics: Metric[];
17
+ monthlyRevenueData: any[];
18
+ }
19
+
20
+ const iconMap: { [key: string]: React.ElementType } = {
21
+ DollarSign: ({ className }: { className?: string }) => (
22
+ <svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
23
+ <line x1="12" y1="1" x2="12" y2="23"></line>
24
+ <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
25
+ </svg>
26
+ ),
27
+ ShoppingCart: ({ className }: { className?: string }) => (
28
+ <svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
29
+ <circle cx="9" cy="21" r="1"></circle>
30
+ <circle cx="20" cy="21" r="1"></circle>
31
+ <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
32
+ </svg>
33
+ ),
34
+ TrendingUp: ({ className }: { className?: string }) => (
35
+ <svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
36
+ <polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
37
+ <polyline points="17 6 23 6 23 12"></polyline>
38
+ </svg>
39
+ ),
40
+ Users: ({ className }: { className?: string }) => (
41
+ <svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
42
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
43
+ <circle cx="9" cy="7" r="4"></circle>
44
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
45
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
46
+ </svg>
47
+ )
48
+ };
49
+
50
+ export const DashboardMetrics: React.FC<DashboardMetricsProps> = ({
51
+ ecommerceMetrics,
52
+ monthlyRevenueData
53
+ }) => {
54
+ return (
55
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
56
+ {ecommerceMetrics.map((metric, index) => {
57
+ const IconComponent = iconMap[metric.icon];
58
+ return (
59
+ <Card key={index}>
60
+ <CardContent className="pt-6">
61
+ <div className="flex items-center justify-between space-y-0 pb-2">
62
+ <CardTitle className="text-sm font-medium text-gray-600">
63
+ {metric.title}
64
+ </CardTitle>
65
+ {IconComponent && <IconComponent className={`h-4 w-4 ${metric.color}`} />}
66
+ </div>
67
+ <div className="flex items-center space-x-2">
68
+ <div className="text-2xl font-bold">{metric.value}</div>
69
+ <div className={`flex items-center text-sm ${
70
+ metric.trend === 'up' ? 'text-green-600' : 'text-red-600'
71
+ }`}>
72
+ {metric.trend === 'up' ? (
73
+ <ArrowUpRight className="h-4 w-4" />
74
+ ) : (
75
+ <ArrowDownRight className="h-4 w-4" />
76
+ )}
77
+ {metric.change}
78
+ </div>
79
+ </div>
80
+ {/* Mini trend chart */}
81
+ {monthlyRevenueData.length > 0 && (
82
+ <div className="mt-4">
83
+ <SparkAreaChart
84
+ data={monthlyRevenueData.slice(-6)} // Last 6 months
85
+ categories={[index === 0 ? "revenue" : "orders"]}
86
+ colors={[index === 0 ? "emerald" : index === 1 ? "blue" : index === 2 ? "violet" : "amber"]}
87
+ className="h-10 w-full"
88
+ />
89
+ </div>
90
+ )}
91
+ </CardContent>
92
+ </Card>
93
+ );
94
+ })}
95
+ </div>
96
+ );
97
+ };