create-bluecopa-react-app 1.0.0 → 1.0.1

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 (62) hide show
  1. package/README.md +69 -28
  2. package/bin/create-bluecopa-react-app.js +1 -1
  3. package/package.json +1 -1
  4. package/templates/latest/.env.example +14 -0
  5. package/templates/latest/.eslintrc.cjs +42 -0
  6. package/templates/latest/README.md +264 -0
  7. package/templates/latest/clean.sh +39 -0
  8. package/templates/latest/index.html +14 -0
  9. package/templates/{basic → latest}/package-lock.json +1099 -1084
  10. package/templates/latest/package.json +61 -0
  11. package/templates/{basic/postcss.config.js → latest/postcss.config.cjs} +1 -1
  12. package/templates/latest/public/bluecopa-logo.svg +30 -0
  13. package/templates/latest/public/favicon-32x32.png +0 -0
  14. package/templates/latest/public/favicon-96x96.png +0 -0
  15. package/templates/latest/public/favicon.ico +0 -0
  16. package/templates/latest/setup.sh +55 -0
  17. package/templates/latest/src/App.tsx +15 -0
  18. package/templates/latest/src/components/layout/dashboard-header.tsx +139 -0
  19. package/templates/latest/src/components/layout/dashboard-layout.tsx +29 -0
  20. package/templates/latest/src/components/layout/sidebar.tsx +54 -0
  21. package/templates/latest/src/components/page/dashboard.tsx +1506 -0
  22. package/templates/latest/src/components/page/navbar.tsx +104 -0
  23. package/templates/latest/src/components/tables/data-grid.tsx +439 -0
  24. package/templates/latest/src/components/ui/alert.tsx +59 -0
  25. package/templates/latest/src/components/ui/avatar.tsx +50 -0
  26. package/templates/latest/src/components/ui/badge.tsx +36 -0
  27. package/templates/latest/src/components/ui/bluecopa-logo.tsx +54 -0
  28. package/templates/{basic → latest}/src/components/ui/button.tsx +5 -11
  29. package/templates/latest/src/components/ui/dropdown-menu.tsx +200 -0
  30. package/templates/latest/src/components/ui/input.tsx +24 -0
  31. package/templates/latest/src/components/ui/label.tsx +23 -0
  32. package/templates/latest/src/components/ui/select.tsx +29 -0
  33. package/templates/latest/src/hooks/use-api.ts +55 -0
  34. package/templates/{basic → latest}/src/index.css +1 -1
  35. package/templates/{basic → latest}/src/main.tsx +6 -2
  36. package/templates/latest/src/pages/Dashboard.tsx +13 -0
  37. package/templates/latest/src/pages/Home.tsx +622 -0
  38. package/templates/latest/src/providers/query-provider.tsx +48 -0
  39. package/templates/latest/src/single-spa.tsx +105 -0
  40. package/templates/{basic/src/types/index.ts → latest/src/types/api.ts} +19 -35
  41. package/templates/latest/src/vite-env.d.ts +11 -0
  42. package/templates/{basic → latest}/tailwind.config.js +15 -4
  43. package/templates/{basic/tsconfig.json → latest/tsconfig.app.json} +5 -10
  44. package/templates/latest/tsconfig.json +19 -0
  45. package/templates/latest/vite.config.ts +64 -0
  46. package/templates/basic/.editorconfig +0 -12
  47. package/templates/basic/.env.example +0 -10
  48. package/templates/basic/README.md +0 -213
  49. package/templates/basic/index.html +0 -13
  50. package/templates/basic/package.json +0 -68
  51. package/templates/basic/setup.sh +0 -46
  52. package/templates/basic/src/App.tsx +0 -95
  53. package/templates/basic/src/components/dashboard/AdvancedAnalytics.tsx +0 -351
  54. package/templates/basic/src/components/dashboard/MetricsOverview.tsx +0 -150
  55. package/templates/basic/src/components/dashboard/TransactionCharts.tsx +0 -215
  56. package/templates/basic/src/components/dashboard/TransactionTable.tsx +0 -172
  57. package/templates/basic/src/components/ui/tabs.tsx +0 -53
  58. package/templates/basic/src/pages/Dashboard.tsx +0 -135
  59. package/templates/basic/vite.config.ts +0 -13
  60. /package/templates/{basic → latest}/src/components/ui/card.tsx +0 -0
  61. /package/templates/{basic → latest}/src/lib/utils.ts +0 -0
  62. /package/templates/{basic → latest}/tsconfig.node.json +0 -0
@@ -1,46 +0,0 @@
1
- #!/bin/bash
2
-
3
- # BlueCopa React Dashboard Template Setup Script
4
- # This script sets up the BlueCopa React dashboard template
5
-
6
- set -e
7
-
8
- PROJECT_NAME=${1:-bluecopa-dashboard}
9
- TEMPLATE_DIR="$(dirname "$0")"
10
-
11
- echo "🚀 Setting up BlueCopa React Dashboard: $PROJECT_NAME"
12
-
13
- # Create project directory
14
- if [ -d "$PROJECT_NAME" ]; then
15
- echo "❌ Directory $PROJECT_NAME already exists"
16
- exit 1
17
- fi
18
-
19
- # Copy template files
20
- echo "📁 Creating project structure..."
21
- cp -r "$TEMPLATE_DIR" "$PROJECT_NAME"
22
- cd "$PROJECT_NAME"
23
-
24
- # Remove setup script from the new project
25
- rm -f setup.sh
26
-
27
- # Create .env file from example
28
- if [ -f ".env.example" ]; then
29
- cp .env.example .env
30
- echo "📝 Created .env file from template"
31
- fi
32
-
33
- # Initialize git repository
34
- echo "🔧 Initializing git repository..."
35
- git init
36
- git add .
37
- git commit -m "Initial commit: BlueCopa React Dashboard template"
38
-
39
- echo "✅ Project setup complete!"
40
- echo ""
41
- echo "Next steps:"
42
- echo "1. cd $PROJECT_NAME"
43
- echo "2. npm install"
44
- echo "3. npm run dev"
45
- echo ""
46
- echo "🎉 Happy coding with BlueCopa!"
@@ -1,95 +0,0 @@
1
- import { reactQuery, ReactQueryDevtools, copaSetConfig } from '@bluecopa/react';
2
- import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3
- import Dashboard from './pages/Dashboard';
4
- import './index.css';
5
- import { useEffect } from 'react';
6
-
7
- const {QueryClient, QueryClientProvider} = reactQuery
8
- // Create a client
9
- const queryClient = new QueryClient({
10
- defaultOptions: {
11
- queries: {
12
- staleTime: 1000 * 60 * 5, // 5 minutes
13
- // cacheTime: 1000 * 60 * 10, // 10 minutes (removed, not a valid property)
14
- },
15
- },
16
- });
17
-
18
- function App() {
19
-
20
- useEffect(() => {
21
- let config = {} as any;
22
- try {
23
- config = JSON.parse(
24
- atob(
25
- 'eyJhY2Nlc3NUb2tlbiI6ImV5SnJhV1FpT2lJd1pHSXpNemRoWXkwek9EVm1MVFJpT0RndFltWm1NQzFrTURJek5HUTFNamMxWlRnaUxDSmhiR2NpT2lKU1V6STFOaUo5LmV5SnpkV0lpT2lJd1ZFOXNiVlZ4ZURCVVMzTmxWRzQ0VnpablZ5SXNJbk4xWW1wbFkzUmZkSGx3WlNJNklsVnpaWElpTENKcGMzTWlPaUpvZEhSd2N6b3ZMMkpzZFdWamIzQmhMbU52YlNJc0lteGhjM1JmYm1GdFpTSTZJblJsYzNScGJtZGtaWFpBWW14MVpXTnZjR0V1WTI5dElpd2lkRzlyWlc1ZmRIbHdaU0k2SWtGalkyVnpjMVJ2YTJWdUlpd2liM0puWDJsa0lqb2lNRlJQYkd4VE9HY3pNa04xYmpoaVdsaDNXR3NpTENKcFpIQmZZV05qWlhOelgzUnZhMlZ1SWpvaU1ERkxORU0wVXpSSFJEWkRVVGhPVFUxR1JsTkJXbFpDUVRnaUxDSmxlSEFpT2pFM05UY3dPVE16T0RJc0ltbGhkQ0k2TVRjMU56QTROakU0TWl3aVptbHljM1JmYm1GdFpTSTZJblJsYzNScGJtZGtaWFpBWW14MVpXTnZjR0V1WTI5dElpd2lhblJwSWpvaU1GVjJkMEZaVUhwTU1VTm5NVlpUUkRKWFNqY2lMQ0psYldGcGJDSTZJblJsYzNScGJtZGtaWFpBWW14MVpXTnZjR0V1WTI5dEluMC51V2p3cHBCeExCNm95Q1JrSVdacUhmNGllTmVGQlFKXzdkRVRpelNCT0lwZUw2N1BKVzE1SUJhZFlPaTBnUm1PS3F2ZTlqXy00a2pUaFhBZnJxSTZ3M01ZUXkxT2pfV09NbS1LdEVNZDFxWDRoQmVhcDZJdWdWOURXNkpuT01lZ19iZUZhU3pfLTNDWWd4dlgxc3hna3BQUm9hZkpvdjZhMXU5VmV2aDlibTdabjFTbzZFeGp6RDZDa2toX0JDbkVyM1hmcWd0TGdvOHRlXzVuVVlFa1ZRdk5TSW9SNFFNWEFCbk9mTDA0WEp2VHBXS05DY1ZGTUJVYkxsNnZHNUpxUEhmbjNzTU5YbTdMTVN3OFdTaWV6elFQa2NnS0hvNy1XRk5HSUZYdmgwRFhtUEwzeloxVkljVE9senAyZWI5NWFiMnBfdEN5NUt0QjhrbFMyUzVheThKLTExcDRfYzlPYkxFZGJoWmZJRWpYbmlNMUhvWmtHcDhTN1VadG5fUDBtcFBYQlRmQVNob29YbEEwaDZqcVg0am5XaktsaDBPY1lNTlRLbWFfYk84Y2lHb2xxdkU0enljaGdZTEs5a2FWN0Jrcm9wbXJpTGlEMnNULU0tMlY5X0ZRbDZRdFdnMnllRl9SSUdqOGl3b194MUFxWjlDWjFTamZVVmFoSDFVWXQ0NlhOWkYyX0pjdnhEZ19tcE9vRU9kbjRQSDJRWlVSZS1xX0ZUdXdmN284TEg2R3IwdmNOWVpVWXBuMTBWaFRPbFFRZWlRYlQycmZySDVhallmaTY1RVBUZFQ2b1RNdjFxalo4ZTRTRzV2SVFsWkRYVW5fUE1GNlgtTk04NDZBWkd3M092TWtGMlNvdFR0S0Fvb1MxX0dMRDExd3ltZWNaSk96c2FyVF96ZyIsInJlZnJlc2hUb2tlbiI6IkFRSUNBSGd5VlBZaGhhUkFnU29PUk5CbjBBR205WmF4WktUakxVNHBXY3NiMGVyU0NRSDJYYm12MHU2Qk5DMVlTUVA3Z0dnOEFBQUIvRENDQWZnR0NTcUdTSWIzRFFFSEJxQ0NBZWt3Z2dIbEFnRUFNSUlCM2dZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF6bk9HSGhjOFdKYXpPdUFQc0NBUkNBZ2dHdlBsYnNieEYwVlNSMllVc1F0bWZ5OWdiVmZaS1NTMU04ZUJCaXdjRU9NdjZzS0lZQzFtZGdtM21rZkJWY3pMb0Z0WThYTjIrQlpoREdWN0dBSmkzeDJOZFJKVmpWMHN4MmN4RWlDNmFOZll1cWFHbnFFZW5oN3VycFhIS1dnVjRMcUxLQi9TVmtnV2pNWWxnYjVXY2FOM0ZucjN5d0VEV0psaDRXYjB4NmJjVFFyRTdnb3lpRm9jN3VTb0VkNzh0YkdsdW1GdXNNSXhMTzR2R2x4UG96UThxYS8wSVZ0dThrYUJVamNhbU9uVHQ5UHUvUHBwV0xHeEN1M24xUzVBa21VZmxzRkFvMkNOM3kwNklyb0oxYmdWTEpRdFhZN0dab0VYQjhwWUY1UWpJMUVCZ1BzQ0N1eTZ0VldrMnk3YzRIYzdlUHBiMm9KRVMycE53WEZqNUtZU1MzaC85cVVLM284Z3didHkzeEtac290dGVJc2N3S21hVGJBWitVN2srM2Nkb3dUUVZ1b0tKSk9HeXdEM1piYkNSVkhKYjQrQUZ4aWc0ZG5YUW1PTU5aNTZaeVhQQTFqSmNOc1Izc0pRWWNGWCtQY1lOcXdYRmdYTjl6czZleTkxRU9lcUV1WGNCR2xjMVhSRlBCaEE3bUw2NEQ0VjE5UXVJMm0ySkkybkNwMzh6bmtaMVpDZlhSQnVLdE5HWmpWU2wzMit4N3VjUDlsQ2QxSnlDNzY2YS96TVQ4YjJPeGtPbHdISnVoa2pBPSIsIm9yZ0lkIjoib3JnXzAxR0U2Rzk3RUVaNU5LQlhTV0VZRUdaSEVLIiwiZW1haWwiOiJ0ZXN0aW5nZGV2QGJsdWVjb3BhLmNvbSIsIndvcmtzcGFjZUlkIjoicHJvZCIsInVzZXJJZCI6IjBUT2xtVXF4MFRLc2VUbjhXNmdXIiwidXNlck5hbWUiOiJ0ZXN0aW5nZGV2QGJsdWVjb3BhLmNvbSB0ZXN0aW5nZGV2QGJsdWVjb3BhLmNvbSIsIndlYlVybCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9zZXR0aW5ncy9kZXZpY2VzIiwiZGF0YVBsYW5lIjoiaHR0cHM6Ly9hcGktZGV2ZWxvcC5ibHVlY29wYS5jb20vZGF0YS1wbGFuZS1hcGkiLCJkZngiOiJodHRwczovL2FwaS1kZXZlbG9wLmJsdWVjb3BhLmNvbS9kZngiLCJhdXRoeiI6Imh0dHBzOi8vZGV2YXBpLmJsdWVjb3BhLmNvbS9hdXRoei1hcGkiLCJjb25uZWN0b3JBdXRoeiI6Imh0dHBzOi8vZGV2YXBpLmJsdWVjb3BhLmNvbS9hdXRoei1hcGkifQ=='
26
- )
27
- );
28
- } catch (error) {
29
- console.log(error);
30
- }
31
- copaSetConfig({
32
- apiBaseUrl: "http://localhost:3000/api/v1",
33
- accessToken: config.accessToken,
34
- workspaceId: config.workspaceId
35
- });
36
- }, []);
37
-
38
- return (
39
- <QueryClientProvider client={queryClient}>
40
- <Router>
41
- <div className="min-h-screen bg-background">
42
- <header className="border-b">
43
- <div className="container mx-auto px-4 py-4">
44
- <div className="flex items-center justify-between">
45
- <div className="flex items-center space-x-4">
46
- <h1 className="text-2xl font-bold text-primary">BlueCopa</h1>
47
- <span className="text-sm text-muted-foreground">Analytics Dashboard</span>
48
- </div>
49
- <nav className="flex items-center space-x-6">
50
- <a href="/" className="text-sm font-medium text-foreground hover:text-primary">
51
- Dashboard
52
- </a>
53
- <a href="/analytics" className="text-sm font-medium text-muted-foreground hover:text-primary">
54
- Analytics
55
- </a>
56
- <a href="/reports" className="text-sm font-medium text-muted-foreground hover:text-primary">
57
- Reports
58
- </a>
59
- </nav>
60
- </div>
61
- </div>
62
- </header>
63
-
64
- <main className="container mx-auto px-4 py-8">
65
- <Routes>
66
- <Route path="/" element={<Dashboard />} />
67
- <Route path="/dashboard" element={<Dashboard />} />
68
- </Routes>
69
- </main>
70
-
71
- <footer className="border-t mt-auto">
72
- <div className="container mx-auto px-4 py-6">
73
- <div className="flex items-center justify-between">
74
- <p className="text-sm text-muted-foreground">
75
- © 2025 BlueCopa. Built with React, TanStack Query, and shadcn/ui.
76
- </p>
77
- <div className="flex items-center space-x-4">
78
- <a href="#" className="text-sm text-muted-foreground hover:text-primary">
79
- Documentation
80
- </a>
81
- <a href="#" className="text-sm text-muted-foreground hover:text-primary">
82
- Support
83
- </a>
84
- </div>
85
- </div>
86
- </div>
87
- </footer>
88
- </div>
89
- </Router>
90
- <ReactQueryDevtools initialIsOpen={false} />
91
- </QueryClientProvider>
92
- );
93
- }
94
-
95
- export default App;
@@ -1,351 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import { useDatasetSample } from '@bluecopa/react';
3
- import { ComposedChart, Line, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
4
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
5
- import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
6
- import { Button } from '@/components/ui/button';
7
- import { RefreshCw, TrendingUp, Calendar, Filter } from 'lucide-react';
8
- import { format, parseISO } from 'date-fns';
9
-
10
- interface AdvancedAnalyticsData {
11
- month: string;
12
- revenue: number;
13
- transactions: number;
14
- averageOrderValue: number;
15
- onlinePercentage: number;
16
- }
17
-
18
- export const AdvancedAnalytics: React.FC = () => {
19
- const {
20
- data: datasetData,
21
- isLoading,
22
- error,
23
- refetch
24
- } = useDatasetSample("0UvoE3CHwqYqzrbGdgs1_large_transaction_dataset_csv");
25
-
26
- // Process data for advanced analytics
27
- const analyticsData = useMemo<AdvancedAnalyticsData[]>(() => {
28
- if (!datasetData?.data?.data) return [];
29
-
30
- const transactions = datasetData.data.data;
31
-
32
- // Group by month
33
- const monthlyData = new Map<string, {
34
- revenue: number;
35
- transactions: number;
36
- onlineTransactions: number;
37
- }>();
38
-
39
- transactions.forEach(transaction => {
40
- const date = parseISO(transaction.transaction_day);
41
- const monthKey = format(date, 'yyyy-MM');
42
-
43
- if (monthlyData.has(monthKey)) {
44
- const existing = monthlyData.get(monthKey)!;
45
- existing.revenue += transaction.total_amount;
46
- existing.transactions += 1;
47
- if (transaction.is_online) {
48
- existing.onlineTransactions += 1;
49
- }
50
- } else {
51
- monthlyData.set(monthKey, {
52
- revenue: transaction.total_amount,
53
- transactions: 1,
54
- onlineTransactions: transaction.is_online ? 1 : 0
55
- });
56
- }
57
- });
58
-
59
- // Convert to array and calculate metrics
60
- return Array.from(monthlyData.entries())
61
- .map(([month, data]) => ({
62
- month: format(parseISO(`${month}-01`), 'MMM yyyy'),
63
- revenue: Math.round(data.revenue),
64
- transactions: data.transactions,
65
- averageOrderValue: Math.round(data.revenue / data.transactions),
66
- onlinePercentage: Math.round((data.onlineTransactions / data.transactions) * 100)
67
- }))
68
- .sort((a, b) => a.month.localeCompare(b.month));
69
- }, [datasetData]);
70
-
71
- // Calculate growth metrics
72
- const growthMetrics = useMemo(() => {
73
- if (analyticsData.length < 2) return null;
74
-
75
- const current = analyticsData[analyticsData.length - 1];
76
- const previous = analyticsData[analyticsData.length - 2];
77
-
78
- return {
79
- revenueGrowth: ((current.revenue - previous.revenue) / previous.revenue) * 100,
80
- transactionGrowth: ((current.transactions - previous.transactions) / previous.transactions) * 100,
81
- aovGrowth: ((current.averageOrderValue - previous.averageOrderValue) / previous.averageOrderValue) * 100
82
- };
83
- }, [analyticsData]);
84
-
85
- // Top performing categories
86
- const categoryPerformance = useMemo(() => {
87
- if (!datasetData?.data?.data) return [];
88
-
89
- const categoryMap = new Map<string, { revenue: number; transactions: number }>();
90
-
91
- datasetData.data.data.forEach(transaction => {
92
- const category = transaction.category;
93
- if (categoryMap.has(category)) {
94
- const existing = categoryMap.get(category)!;
95
- existing.revenue += transaction.total_amount;
96
- existing.transactions += 1;
97
- } else {
98
- categoryMap.set(category, {
99
- revenue: transaction.total_amount,
100
- transactions: 1
101
- });
102
- }
103
- });
104
-
105
- return Array.from(categoryMap.entries())
106
- .map(([category, data]) => ({
107
- category,
108
- revenue: Math.round(data.revenue),
109
- transactions: data.transactions,
110
- averageOrderValue: Math.round(data.revenue / data.transactions)
111
- }))
112
- .sort((a, b) => b.revenue - a.revenue)
113
- .slice(0, 5);
114
- }, [datasetData]);
115
-
116
- if (isLoading) {
117
- return (
118
- <div className="space-y-6">
119
- {[1, 2, 3].map((i) => (
120
- <Card key={i}>
121
- <CardHeader>
122
- <div className="h-6 w-48 bg-muted animate-pulse rounded"></div>
123
- </CardHeader>
124
- <CardContent>
125
- <div className="h-64 bg-muted animate-pulse rounded"></div>
126
- </CardContent>
127
- </Card>
128
- ))}
129
- </div>
130
- );
131
- }
132
-
133
- if (error) {
134
- return (
135
- <Card>
136
- <CardHeader>
137
- <CardTitle className="text-red-600">Error Loading Analytics</CardTitle>
138
- </CardHeader>
139
- <CardContent>
140
- <p className="text-muted-foreground mb-4">
141
- Failed to load analytics data. Please try refreshing.
142
- </p>
143
- <Button onClick={() => refetch()} className="flex items-center gap-2">
144
- <RefreshCw className="h-4 w-4" />
145
- Retry
146
- </Button>
147
- </CardContent>
148
- </Card>
149
- );
150
- }
151
-
152
- return (
153
- <div className="space-y-6">
154
- {/* Growth Metrics Header */}
155
- {growthMetrics && (
156
- <div className="grid gap-4 md:grid-cols-3">
157
- <Card>
158
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
159
- <CardTitle className="text-sm font-medium">Revenue Growth</CardTitle>
160
- <TrendingUp className="h-4 w-4 text-muted-foreground" />
161
- </CardHeader>
162
- <CardContent>
163
- <div className={`text-2xl font-bold ${growthMetrics.revenueGrowth >= 0 ? 'text-green-600' : 'text-red-600'}`}>
164
- {growthMetrics.revenueGrowth >= 0 ? '+' : ''}{growthMetrics.revenueGrowth.toFixed(1)}%
165
- </div>
166
- <p className="text-xs text-muted-foreground">vs previous month</p>
167
- </CardContent>
168
- </Card>
169
-
170
- <Card>
171
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
172
- <CardTitle className="text-sm font-medium">Transaction Growth</CardTitle>
173
- <Calendar className="h-4 w-4 text-muted-foreground" />
174
- </CardHeader>
175
- <CardContent>
176
- <div className={`text-2xl font-bold ${growthMetrics.transactionGrowth >= 0 ? 'text-green-600' : 'text-red-600'}`}>
177
- {growthMetrics.transactionGrowth >= 0 ? '+' : ''}{growthMetrics.transactionGrowth.toFixed(1)}%
178
- </div>
179
- <p className="text-xs text-muted-foreground">vs previous month</p>
180
- </CardContent>
181
- </Card>
182
-
183
- <Card>
184
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
185
- <CardTitle className="text-sm font-medium">AOV Growth</CardTitle>
186
- <Filter className="h-4 w-4 text-muted-foreground" />
187
- </CardHeader>
188
- <CardContent>
189
- <div className={`text-2xl font-bold ${growthMetrics.aovGrowth >= 0 ? 'text-green-600' : 'text-red-600'}`}>
190
- {growthMetrics.aovGrowth >= 0 ? '+' : ''}{growthMetrics.aovGrowth.toFixed(1)}%
191
- </div>
192
- <p className="text-xs text-muted-foreground">vs previous month</p>
193
- </CardContent>
194
- </Card>
195
- </div>
196
- )}
197
-
198
- {/* Advanced Charts */}
199
- <Tabs defaultValue="trends" className="space-y-4">
200
- <TabsList>
201
- <TabsTrigger value="trends">Revenue Trends</TabsTrigger>
202
- <TabsTrigger value="performance">Category Performance</TabsTrigger>
203
- <TabsTrigger value="insights">Business Insights</TabsTrigger>
204
- </TabsList>
205
-
206
- <TabsContent value="trends" className="space-y-4">
207
- <Card>
208
- <CardHeader>
209
- <CardTitle>Revenue & Transaction Trends</CardTitle>
210
- <CardDescription>
211
- Monthly revenue, transaction count, and average order value over time
212
- </CardDescription>
213
- </CardHeader>
214
- <CardContent>
215
- <ResponsiveContainer width="100%" height={400}>
216
- <ComposedChart data={analyticsData}>
217
- <CartesianGrid strokeDasharray="3 3" />
218
- <XAxis dataKey="month" />
219
- <YAxis yAxisId="left" />
220
- <YAxis yAxisId="right" orientation="right" />
221
- <Tooltip
222
- formatter={(value: number, name: string) => {
223
- if (name === 'revenue') return [`₹${value.toLocaleString()}`, 'Revenue'];
224
- if (name === 'averageOrderValue') return [`₹${value}`, 'AOV'];
225
- if (name === 'onlinePercentage') return [`${value}%`, 'Online %'];
226
- return [value, name];
227
- }}
228
- />
229
- <Legend />
230
- <Bar yAxisId="left" dataKey="revenue" fill="#8884d8" name="Revenue" />
231
- <Line yAxisId="right" type="monotone" dataKey="averageOrderValue" stroke="#82ca9d" name="Average Order Value" />
232
- <Line yAxisId="right" type="monotone" dataKey="onlinePercentage" stroke="#ffc658" name="Online %" />
233
- </ComposedChart>
234
- </ResponsiveContainer>
235
- </CardContent>
236
- </Card>
237
- </TabsContent>
238
-
239
- <TabsContent value="performance" className="space-y-4">
240
- <Card>
241
- <CardHeader>
242
- <CardTitle>Top Performing Categories</CardTitle>
243
- <CardDescription>
244
- Revenue and transaction metrics by product category
245
- </CardDescription>
246
- </CardHeader>
247
- <CardContent>
248
- <div className="space-y-4">
249
- {categoryPerformance.map((category, index) => (
250
- <div key={category.category} className="flex items-center justify-between p-4 border rounded-lg">
251
- <div className="flex items-center space-x-4">
252
- <div className="w-8 h-8 bg-primary/10 rounded-full flex items-center justify-center">
253
- <span className="text-sm font-bold text-primary">#{index + 1}</span>
254
- </div>
255
- <div>
256
- <h4 className="font-medium">{category.category}</h4>
257
- <p className="text-sm text-muted-foreground">
258
- {category.transactions} transactions
259
- </p>
260
- </div>
261
- </div>
262
- <div className="text-right">
263
- <div className="font-bold">₹{category.revenue.toLocaleString()}</div>
264
- <div className="text-sm text-muted-foreground">
265
- ₹{category.averageOrderValue} AOV
266
- </div>
267
- </div>
268
- </div>
269
- ))}
270
- </div>
271
- </CardContent>
272
- </Card>
273
- </TabsContent>
274
-
275
- <TabsContent value="insights" className="space-y-4">
276
- <div className="grid gap-4 md:grid-cols-2">
277
- <Card>
278
- <CardHeader>
279
- <CardTitle>Key Insights</CardTitle>
280
- </CardHeader>
281
- <CardContent className="space-y-4">
282
- <div className="flex items-start space-x-3">
283
- <div className="w-2 h-2 bg-green-500 rounded-full mt-2"></div>
284
- <div>
285
- <h4 className="font-medium">Strong Online Growth</h4>
286
- <p className="text-sm text-muted-foreground">
287
- Online transactions showing consistent upward trend across all categories
288
- </p>
289
- </div>
290
- </div>
291
- <div className="flex items-start space-x-3">
292
- <div className="w-2 h-2 bg-blue-500 rounded-full mt-2"></div>
293
- <div>
294
- <h4 className="font-medium">Electronics Leading</h4>
295
- <p className="text-sm text-muted-foreground">
296
- Electronics category driving highest revenue with premium pricing
297
- </p>
298
- </div>
299
- </div>
300
- <div className="flex items-start space-x-3">
301
- <div className="w-2 h-2 bg-yellow-500 rounded-full mt-2"></div>
302
- <div>
303
- <h4 className="font-medium">Seasonal Patterns</h4>
304
- <p className="text-sm text-muted-foreground">
305
- Clear seasonal trends visible in transaction volume and AOV
306
- </p>
307
- </div>
308
- </div>
309
- </CardContent>
310
- </Card>
311
-
312
- <Card>
313
- <CardHeader>
314
- <CardTitle>Recommendations</CardTitle>
315
- </CardHeader>
316
- <CardContent className="space-y-4">
317
- <div className="flex items-start space-x-3">
318
- <div className="w-2 h-2 bg-purple-500 rounded-full mt-2"></div>
319
- <div>
320
- <h4 className="font-medium">Optimize Mobile Experience</h4>
321
- <p className="text-sm text-muted-foreground">
322
- Focus on mobile app optimization to capture growing online segment
323
- </p>
324
- </div>
325
- </div>
326
- <div className="flex items-start space-x-3">
327
- <div className="w-2 h-2 bg-orange-500 rounded-full mt-2"></div>
328
- <div>
329
- <h4 className="font-medium">Category Expansion</h4>
330
- <p className="text-sm text-muted-foreground">
331
- Consider expanding grocery and furniture offerings
332
- </p>
333
- </div>
334
- </div>
335
- <div className="flex items-start space-x-3">
336
- <div className="w-2 h-2 bg-red-500 rounded-full mt-2"></div>
337
- <div>
338
- <h4 className="font-medium">Payment Incentives</h4>
339
- <p className="text-sm text-muted-foreground">
340
- Promote UPI payments with targeted incentives
341
- </p>
342
- </div>
343
- </div>
344
- </CardContent>
345
- </Card>
346
- </div>
347
- </TabsContent>
348
- </Tabs>
349
- </div>
350
- );
351
- };
@@ -1,150 +0,0 @@
1
- import React from 'react';
2
- import { useMetric, useDatasetSample } from '@bluecopa/react';
3
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
4
- import { Button } from '@/components/ui/button';
5
- import { RefreshCw, TrendingUp, Package, CreditCard, Users } from 'lucide-react';
6
-
7
- interface MetricCardProps {
8
- title: string;
9
- value: string | number;
10
- description?: string;
11
- icon?: React.ReactNode;
12
- trend?: {
13
- value: number;
14
- isPositive: boolean;
15
- };
16
- }
17
-
18
- const MetricCard: React.FC<MetricCardProps> = ({ title, value, description, icon, trend }) => {
19
- return (
20
- <Card>
21
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
22
- <CardTitle className="text-sm font-medium">{title}</CardTitle>
23
- {icon}
24
- </CardHeader>
25
- <CardContent>
26
- <div className="text-2xl font-bold">{typeof value === 'number' ? value.toLocaleString() : value}</div>
27
- {description && (
28
- <p className="text-xs text-muted-foreground">{description}</p>
29
- )}
30
- {trend && (
31
- <div className={`flex items-center text-xs ${trend.isPositive ? 'text-green-600' : 'text-red-600'}`}>
32
- <TrendingUp className="mr-1 h-3 w-3" />
33
- {trend.isPositive ? '+' : ''}{trend.value}% from last month
34
- </div>
35
- )}
36
- </CardContent>
37
- </Card>
38
- );
39
- };
40
-
41
- export const MetricsOverview: React.FC = () => {
42
- const {
43
- data: metricData,
44
- isLoading: metricLoading,
45
- error: metricError,
46
- refetch: refetchMetric
47
- } = useMetric("0UbwCn75pym1N47D89uS");
48
-
49
- const {
50
- data: datasetData,
51
- isLoading: datasetLoading,
52
- error: datasetError,
53
- } = useDatasetSample("0UvoE3CHwqYqzrbGdgs1_large_transaction_dataset_csv");
54
-
55
- // Calculate metrics from transaction data
56
- const calculateMetrics = () => {
57
- if (!datasetData?.data?.data) return null;
58
-
59
- const transactions = datasetData.data.data;
60
- const totalTransactions = transactions.length;
61
- const totalRevenue = transactions.reduce((sum, t) => sum + t.total_amount, 0);
62
- const avgOrderValue = totalRevenue / totalTransactions;
63
- const uniqueProducts = new Set(transactions.map(t => t.product_id)).size;
64
-
65
- return {
66
- totalTransactions,
67
- totalRevenue,
68
- avgOrderValue,
69
- uniqueProducts
70
- };
71
- };
72
-
73
- const metrics = calculateMetrics();
74
-
75
- if (metricLoading || datasetLoading) {
76
- return (
77
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
78
- {[1, 2, 3, 4].map((i) => (
79
- <Card key={i}>
80
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
81
- <div className="h-4 w-20 bg-muted animate-pulse rounded"></div>
82
- <div className="h-4 w-4 bg-muted animate-pulse rounded"></div>
83
- </CardHeader>
84
- <CardContent>
85
- <div className="h-8 w-24 bg-muted animate-pulse rounded mb-2"></div>
86
- <div className="h-3 w-32 bg-muted animate-pulse rounded"></div>
87
- </CardContent>
88
- </Card>
89
- ))}
90
- </div>
91
- );
92
- }
93
-
94
- if (metricError || datasetError) {
95
- return (
96
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
97
- <Card className="md:col-span-2 lg:col-span-4">
98
- <CardHeader>
99
- <CardTitle className="text-red-600">Error Loading Metrics</CardTitle>
100
- </CardHeader>
101
- <CardContent>
102
- <p className="text-muted-foreground mb-4">
103
- Failed to load dashboard metrics. Please try refreshing.
104
- </p>
105
- <Button onClick={() => refetchMetric()} className="flex items-center gap-2">
106
- <RefreshCw className="h-4 w-4" />
107
- Retry
108
- </Button>
109
- </CardContent>
110
- </Card>
111
- </div>
112
- );
113
- }
114
-
115
- return (
116
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
117
- <MetricCard
118
- title="Total Revenue"
119
- value={`₹${metricData?.data[0]?.TotalAmount?.toLocaleString() || 0}`}
120
- description="Total amount from all transactions"
121
- icon={<TrendingUp className="h-4 w-4 text-muted-foreground" />}
122
- trend={{ value: 12.5, isPositive: true }}
123
- />
124
-
125
- <MetricCard
126
- title="Total Transactions"
127
- value={metrics?.totalTransactions || 0}
128
- description="Number of completed transactions"
129
- icon={<CreditCard className="h-4 w-4 text-muted-foreground" />}
130
- trend={{ value: 8.2, isPositive: true }}
131
- />
132
-
133
- <MetricCard
134
- title="Average Order Value"
135
- value={`₹${metrics?.avgOrderValue?.toFixed(2) || 0}`}
136
- description="Average transaction amount"
137
- icon={<Package className="h-4 w-4 text-muted-foreground" />}
138
- trend={{ value: -2.1, isPositive: false }}
139
- />
140
-
141
- <MetricCard
142
- title="Unique Products"
143
- value={metrics?.uniqueProducts || 0}
144
- description="Number of different products sold"
145
- icon={<Users className="h-4 w-4 text-muted-foreground" />}
146
- trend={{ value: 5.7, isPositive: true }}
147
- />
148
- </div>
149
- );
150
- };