create-bluecopa-react-app 1.0.3 → 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.
- package/README.md +76 -238
- package/package.json +1 -1
- package/templates/latest/{Agent.md → AGENT.md} +21 -27
- package/templates/latest/README.md +15 -38
- package/templates/latest/clean.sh +1 -0
- package/templates/latest/package-lock.json +8 -8
- package/templates/latest/package.json +1 -1
- package/templates/latest/setup.sh +5 -4
- package/templates/latest/src/App.tsx +11 -7
- package/templates/latest/src/components/charts/AreaChart.tsx +80 -0
- package/templates/latest/src/components/charts/DonutChart.tsx +73 -0
- package/templates/latest/src/components/charts/SparkAreaChart.tsx +52 -0
- package/templates/latest/src/components/layout/dashboard-header.tsx +1 -1
- package/templates/latest/src/components/layout/dashboard-layout.tsx +19 -11
- package/templates/latest/src/components/{page → layout}/navbar.tsx +2 -0
- package/templates/latest/src/components/layout/sidebar.tsx +2 -1
- package/templates/latest/src/components/page/dashboard/DashboardMetrics.tsx +97 -0
- package/templates/latest/src/components/page/dashboard/PaymentMethodsAnalysis.tsx +182 -0
- package/templates/latest/src/components/page/dashboard/RevenueAnalytics.tsx +505 -0
- package/templates/latest/src/components/page/dashboard/SalesAnalytics.tsx +313 -0
- package/templates/latest/src/components/page/dashboard/TransactionsTable.tsx +256 -0
- package/templates/latest/src/components/page/dashboard/dashboard-utils.ts +147 -0
- package/templates/latest/src/components/page/dashboard/dashboard.tsx +185 -0
- package/templates/latest/src/components/ui/bluecopa-logo.tsx +3 -0
- package/templates/latest/src/components/ui/label.tsx +0 -2
- package/templates/latest/src/components/ui/select.tsx +0 -2
- package/templates/latest/src/pages/Dashboard.tsx +1 -1
- package/templates/latest/src/single-spa.tsx +38 -28
- package/templates/latest/tailwind.config.js +1 -2
- package/templates/latest/tsconfig.app.json +6 -0
- package/templates/latest/tsconfig.json +6 -6
- package/templates/latest/tsconfig.node.json +5 -1
- package/templates/latest/vite.config.ts +1 -1
- package/templates/latest/src/components/page/dashboard.tsx +0 -1506
|
@@ -1,1506 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
4
|
-
import { Button } from '@/components/ui/button'
|
|
5
|
-
import { Badge } from '@/components/ui/badge'
|
|
6
|
-
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
7
|
-
import { useMetricDataDemo, useDatasetDataDemo, useUserData } from '@/hooks/use-api'
|
|
8
|
-
import {
|
|
9
|
-
RefreshCw,
|
|
10
|
-
Loader2,
|
|
11
|
-
AlertCircle,
|
|
12
|
-
CheckCircle,
|
|
13
|
-
Clock,
|
|
14
|
-
Users,
|
|
15
|
-
DollarSign,
|
|
16
|
-
ShoppingCart,
|
|
17
|
-
TrendingUp,
|
|
18
|
-
ArrowUpRight,
|
|
19
|
-
ArrowDownRight
|
|
20
|
-
} from 'lucide-react'
|
|
21
|
-
import {
|
|
22
|
-
ResponsiveContainer,
|
|
23
|
-
AreaChart as RechartsAreaChart,
|
|
24
|
-
BarChart as RechartsBarChart,
|
|
25
|
-
PieChart,
|
|
26
|
-
Cell,
|
|
27
|
-
Area,
|
|
28
|
-
Bar,
|
|
29
|
-
XAxis,
|
|
30
|
-
YAxis,
|
|
31
|
-
CartesianGrid,
|
|
32
|
-
Tooltip,
|
|
33
|
-
Legend,
|
|
34
|
-
Pie
|
|
35
|
-
} from 'recharts'
|
|
36
|
-
|
|
37
|
-
// Simple table components since they're not in the UI library
|
|
38
|
-
const Table = ({ children, className = "" }: { children: React.ReactNode, className?: string }) => (
|
|
39
|
-
<div className={`overflow-x-auto ${className}`}>
|
|
40
|
-
<table className="min-w-full divide-y divide-gray-200">
|
|
41
|
-
{children}
|
|
42
|
-
</table>
|
|
43
|
-
</div>
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
const TableHead = ({ children }: { children: React.ReactNode }) => (
|
|
47
|
-
<thead className="bg-gray-50">{children}</thead>
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const TableBody = ({ children }: { children: React.ReactNode }) => (
|
|
51
|
-
<tbody className="bg-white divide-y divide-gray-200">{children}</tbody>
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
const TableRow = ({ children }: { children: React.ReactNode }) => (
|
|
55
|
-
<tr className="hover:bg-gray-50">{children}</tr>
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
const TableHeaderCell = ({ children }: { children: React.ReactNode }) => (
|
|
59
|
-
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
60
|
-
{children}
|
|
61
|
-
</th>
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
const TableCell = ({ children, className = "" }: { children: React.ReactNode, className?: string }) => (
|
|
65
|
-
<td className={`px-6 py-4 whitespace-nowrap text-sm text-gray-900 ${className}`}>
|
|
66
|
-
{children}
|
|
67
|
-
</td>
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
// Custom chart components using Recharts
|
|
71
|
-
const AreaChart = ({
|
|
72
|
-
data,
|
|
73
|
-
index,
|
|
74
|
-
categories,
|
|
75
|
-
colors = ['#3B82F6'],
|
|
76
|
-
className = "h-80",
|
|
77
|
-
valueFormatter,
|
|
78
|
-
showLegend = true,
|
|
79
|
-
showGridLines = true,
|
|
80
|
-
showXAxis = true,
|
|
81
|
-
showYAxis = true,
|
|
82
|
-
yAxisWidth
|
|
83
|
-
}: any) => {
|
|
84
|
-
const colorMap: { [key: string]: string } = {
|
|
85
|
-
blue: '#3B82F6',
|
|
86
|
-
emerald: '#10B981',
|
|
87
|
-
violet: '#8B5CF6',
|
|
88
|
-
rose: '#F43F5E',
|
|
89
|
-
amber: '#F59E0B',
|
|
90
|
-
cyan: '#06B6D4',
|
|
91
|
-
slate: '#64748B',
|
|
92
|
-
indigo: '#6366F1'
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const getColor = (color: string, index: number) => {
|
|
96
|
-
return colorMap[color] || colors[index] || '#3B82F6'
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div className={className}>
|
|
101
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
102
|
-
<RechartsAreaChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
|
103
|
-
{showGridLines && <CartesianGrid strokeDasharray="3 3" />}
|
|
104
|
-
{showXAxis && <XAxis dataKey={index} />}
|
|
105
|
-
{showYAxis && <YAxis width={yAxisWidth || 60} />}
|
|
106
|
-
<Tooltip
|
|
107
|
-
formatter={(value: any) => valueFormatter ? valueFormatter(value) : value}
|
|
108
|
-
/>
|
|
109
|
-
{showLegend && <Legend />}
|
|
110
|
-
{categories?.map((category: string, idx: number) => (
|
|
111
|
-
<Area
|
|
112
|
-
key={category}
|
|
113
|
-
type="monotone"
|
|
114
|
-
dataKey={category}
|
|
115
|
-
stroke={getColor(colors[idx], idx)}
|
|
116
|
-
fill={getColor(colors[idx], idx)}
|
|
117
|
-
fillOpacity={0.3}
|
|
118
|
-
/>
|
|
119
|
-
))}
|
|
120
|
-
</RechartsAreaChart>
|
|
121
|
-
</ResponsiveContainer>
|
|
122
|
-
</div>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const DonutChart = ({
|
|
127
|
-
data,
|
|
128
|
-
category,
|
|
129
|
-
index,
|
|
130
|
-
colors = ['#3B82F6'],
|
|
131
|
-
className = "h-64",
|
|
132
|
-
valueFormatter,
|
|
133
|
-
variant = "donut"
|
|
134
|
-
}: any) => {
|
|
135
|
-
const colorMap: { [key: string]: string } = {
|
|
136
|
-
blue: '#3B82F6',
|
|
137
|
-
emerald: '#10B981',
|
|
138
|
-
violet: '#8B5CF6',
|
|
139
|
-
rose: '#F43F5E',
|
|
140
|
-
amber: '#F59E0B',
|
|
141
|
-
cyan: '#06B6D4',
|
|
142
|
-
slate: '#64748B',
|
|
143
|
-
indigo: '#6366F1'
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const getColor = (color: string, index: number) => {
|
|
147
|
-
return colorMap[color] || colors[index] || '#3B82F6'
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const COLORS = colors.map((color: string, idx: number) => getColor(color, idx))
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<div className={className}>
|
|
154
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
155
|
-
<PieChart>
|
|
156
|
-
<Pie
|
|
157
|
-
data={data}
|
|
158
|
-
cx="50%"
|
|
159
|
-
cy="50%"
|
|
160
|
-
innerRadius={variant === "donut" ? 60 : 0}
|
|
161
|
-
outerRadius={80}
|
|
162
|
-
paddingAngle={5}
|
|
163
|
-
dataKey={category}
|
|
164
|
-
nameKey={index}
|
|
165
|
-
>
|
|
166
|
-
{data.map((_entry: any, index: number) => (
|
|
167
|
-
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
|
168
|
-
))}
|
|
169
|
-
</Pie>
|
|
170
|
-
<Tooltip
|
|
171
|
-
formatter={(value: any) => valueFormatter ? valueFormatter(value) : value}
|
|
172
|
-
/>
|
|
173
|
-
<Legend />
|
|
174
|
-
</PieChart>
|
|
175
|
-
</ResponsiveContainer>
|
|
176
|
-
</div>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const SparkAreaChart = ({
|
|
181
|
-
data,
|
|
182
|
-
categories,
|
|
183
|
-
colors = ['#3B82F6'],
|
|
184
|
-
className = "h-10"
|
|
185
|
-
}: any) => {
|
|
186
|
-
const colorMap: { [key: string]: string } = {
|
|
187
|
-
blue: '#3B82F6',
|
|
188
|
-
emerald: '#10B981',
|
|
189
|
-
violet: '#8B5CF6',
|
|
190
|
-
rose: '#F43F5E',
|
|
191
|
-
amber: '#F59E0B',
|
|
192
|
-
cyan: '#06B6D4'
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const getColor = (color: string, index: number) => {
|
|
196
|
-
return colorMap[color] || colors[index] || '#3B82F6'
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<div className={className}>
|
|
201
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
202
|
-
<RechartsAreaChart data={data} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
|
203
|
-
{categories?.map((category: string, idx: number) => (
|
|
204
|
-
<Area
|
|
205
|
-
key={category}
|
|
206
|
-
type="monotone"
|
|
207
|
-
dataKey={category}
|
|
208
|
-
stroke={getColor(colors[idx], idx)}
|
|
209
|
-
fill={getColor(colors[idx], idx)}
|
|
210
|
-
fillOpacity={0.6}
|
|
211
|
-
/>
|
|
212
|
-
))}
|
|
213
|
-
</RechartsAreaChart>
|
|
214
|
-
</ResponsiveContainer>
|
|
215
|
-
</div>
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export default function Dashboard() {
|
|
220
|
-
const metricQuery = useMetricDataDemo()
|
|
221
|
-
const datasetQuery = useDatasetDataDemo()
|
|
222
|
-
const userQuery = useUserData()
|
|
223
|
-
|
|
224
|
-
// Helper function to render query state
|
|
225
|
-
const renderQueryState = (query: any, title: string) => {
|
|
226
|
-
if (query.isLoading) {
|
|
227
|
-
return (
|
|
228
|
-
<div className="flex items-center gap-2 text-blue-600">
|
|
229
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
230
|
-
<span className="text-sm">{title}: Loading...</span>
|
|
231
|
-
</div>
|
|
232
|
-
)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (query.isError) {
|
|
236
|
-
return (
|
|
237
|
-
<div className="flex items-center gap-2 text-red-600">
|
|
238
|
-
<AlertCircle className="h-4 w-4" />
|
|
239
|
-
<span className="text-sm">{title}: Error</span>
|
|
240
|
-
</div>
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (query.isSuccess) {
|
|
245
|
-
return (
|
|
246
|
-
<div className="flex items-center gap-2 text-green-600">
|
|
247
|
-
<CheckCircle className="h-4 w-4" />
|
|
248
|
-
<span className="text-sm">{title}: Success</span>
|
|
249
|
-
</div>
|
|
250
|
-
)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return (
|
|
254
|
-
<div className="flex items-center gap-2 text-gray-500">
|
|
255
|
-
<Clock className="h-4 w-4" />
|
|
256
|
-
<span className="text-sm">{title}: Idle</span>
|
|
257
|
-
</div>
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Process data for ecommerce metrics and charts
|
|
262
|
-
const rawApiData = datasetQuery.data?.data?.data || []
|
|
263
|
-
|
|
264
|
-
// Fallback sample data when API returns empty data
|
|
265
|
-
const sampleTransactionData = [
|
|
266
|
-
{ transaction_id: '001', customer_name: 'John Doe', total_amount: 1250, category: 'Electronics', payment_method: 'Credit Card', transaction_date: '2024-01-15', state: 'CA' },
|
|
267
|
-
{ transaction_id: '002', customer_name: 'Jane Smith', total_amount: 780, category: 'Clothing', payment_method: 'PayPal', transaction_date: '2024-01-18', state: 'NY' },
|
|
268
|
-
{ transaction_id: '003', customer_name: 'Mike Johnson', total_amount: 2100, category: 'Electronics', payment_method: 'Credit Card', transaction_date: '2024-02-02', state: 'TX' },
|
|
269
|
-
{ transaction_id: '004', customer_name: 'Sarah Wilson', total_amount: 450, category: 'Books', payment_method: 'Debit Card', transaction_date: '2024-02-05', state: 'FL' },
|
|
270
|
-
{ transaction_id: '005', customer_name: 'David Brown', total_amount: 890, category: 'Sports', payment_method: 'Credit Card', transaction_date: '2024-02-10', state: 'CA' },
|
|
271
|
-
{ transaction_id: '006', customer_name: 'Lisa Garcia', total_amount: 1650, category: 'Electronics', payment_method: 'PayPal', transaction_date: '2024-02-15', state: 'WA' },
|
|
272
|
-
{ transaction_id: '007', customer_name: 'Robert Lee', total_amount: 320, category: 'Books', payment_method: 'Cash', transaction_date: '2024-02-20', state: 'OR' },
|
|
273
|
-
{ transaction_id: '008', customer_name: 'Emily Davis', total_amount: 950, category: 'Clothing', payment_method: 'Credit Card', transaction_date: '2024-03-01', state: 'NY' },
|
|
274
|
-
{ transaction_id: '009', customer_name: 'Alex Rodriguez', total_amount: 1480, category: 'Sports', payment_method: 'Debit Card', transaction_date: '2024-03-05', state: 'TX' },
|
|
275
|
-
{ transaction_id: '010', customer_name: 'Maria Martinez', total_amount: 670, category: 'Beauty', payment_method: 'PayPal', transaction_date: '2024-03-08', state: 'FL' },
|
|
276
|
-
{ transaction_id: '011', customer_name: 'Chris Anderson', total_amount: 2200, category: 'Electronics', payment_method: 'Credit Card', transaction_date: '2024-03-12', state: 'CA' },
|
|
277
|
-
{ transaction_id: '012', customer_name: 'Amanda White', total_amount: 520, category: 'Clothing', payment_method: 'Debit Card', transaction_date: '2024-03-15', state: 'WA' },
|
|
278
|
-
{ transaction_id: '013', customer_name: 'James Taylor', total_amount: 1100, category: 'Sports', payment_method: 'Credit Card', transaction_date: '2024-03-18', state: 'OR' },
|
|
279
|
-
{ transaction_id: '014', customer_name: 'Jennifer Moore', total_amount: 380, category: 'Books', payment_method: 'Cash', transaction_date: '2024-03-22', state: 'NY' },
|
|
280
|
-
{ transaction_id: '015', customer_name: 'Kevin Jackson', total_amount: 1850, category: 'Electronics', payment_method: 'PayPal', transaction_date: '2024-03-25', state: 'TX' },
|
|
281
|
-
{ transaction_id: '016', customer_name: 'Nicole Thompson', total_amount: 760, category: 'Beauty', payment_method: 'Credit Card', transaction_date: '2024-04-01', state: 'FL' },
|
|
282
|
-
{ transaction_id: '017', customer_name: 'Brian Wilson', total_amount: 1320, category: 'Sports', payment_method: 'Debit Card', transaction_date: '2024-04-05', state: 'CA' },
|
|
283
|
-
{ transaction_id: '018', customer_name: 'Jessica Clark', total_amount: 590, category: 'Clothing', payment_method: 'Credit Card', transaction_date: '2024-04-08', state: 'WA' },
|
|
284
|
-
{ transaction_id: '019', customer_name: 'Michael Lewis', total_amount: 2400, category: 'Electronics', payment_method: 'PayPal', transaction_date: '2024-04-12', state: 'OR' },
|
|
285
|
-
{ transaction_id: '020', customer_name: 'Ashley Hall', total_amount: 450, category: 'Books', payment_method: 'Cash', transaction_date: '2024-04-15', state: 'NY' }
|
|
286
|
-
]
|
|
287
|
-
|
|
288
|
-
// Use real data if available, otherwise use sample data
|
|
289
|
-
const rawData = rawApiData.length > 0 ? rawApiData : sampleTransactionData
|
|
290
|
-
|
|
291
|
-
const orderData = rawData.reduce((acc: any, transaction: any) => {
|
|
292
|
-
const existing = acc.find((item: any) => item.category === transaction.category)
|
|
293
|
-
if (existing) {
|
|
294
|
-
existing.total += transaction.total_amount
|
|
295
|
-
existing.count += 1
|
|
296
|
-
} else {
|
|
297
|
-
acc.push({
|
|
298
|
-
category: transaction.category,
|
|
299
|
-
total: transaction.total_amount,
|
|
300
|
-
count: 1
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
return acc
|
|
304
|
-
}, [])
|
|
305
|
-
|
|
306
|
-
const paymentMethodData = rawData.reduce((acc: any, transaction: any) => {
|
|
307
|
-
const existing = acc.find((item: any) => item.method === transaction.payment_method)
|
|
308
|
-
if (existing) {
|
|
309
|
-
existing.total += transaction.total_amount
|
|
310
|
-
existing.count += 1
|
|
311
|
-
} else {
|
|
312
|
-
acc.push({
|
|
313
|
-
method: transaction.payment_method,
|
|
314
|
-
total: transaction.total_amount,
|
|
315
|
-
count: 1
|
|
316
|
-
})
|
|
317
|
-
}
|
|
318
|
-
return acc
|
|
319
|
-
}, [])
|
|
320
|
-
|
|
321
|
-
// Process data for time-series charts
|
|
322
|
-
const monthlyRevenueData = rawData.reduce((acc: any, transaction: any) => {
|
|
323
|
-
if (!transaction.transaction_date) return acc
|
|
324
|
-
|
|
325
|
-
const date = new Date(transaction.transaction_date)
|
|
326
|
-
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
|
327
|
-
|
|
328
|
-
const existing = acc.find((item: any) => item.month === monthKey)
|
|
329
|
-
if (existing) {
|
|
330
|
-
existing.revenue += transaction.total_amount
|
|
331
|
-
existing.orders += 1
|
|
332
|
-
} else {
|
|
333
|
-
acc.push({
|
|
334
|
-
month: monthKey,
|
|
335
|
-
revenue: transaction.total_amount,
|
|
336
|
-
orders: 1
|
|
337
|
-
})
|
|
338
|
-
}
|
|
339
|
-
return acc
|
|
340
|
-
}, []).sort((a: any, b: any) => a.month.localeCompare(b.month))
|
|
341
|
-
|
|
342
|
-
// Process data for tables
|
|
343
|
-
const recentTransactions = rawData
|
|
344
|
-
.slice(0, 10)
|
|
345
|
-
.map((transaction: any) => ({
|
|
346
|
-
id: transaction.transaction_id,
|
|
347
|
-
customer: transaction.customer_name || 'N/A',
|
|
348
|
-
amount: transaction.total_amount,
|
|
349
|
-
category: transaction.category,
|
|
350
|
-
payment_method: transaction.payment_method,
|
|
351
|
-
date: transaction.transaction_date ? new Date(transaction.transaction_date).toLocaleDateString() : 'N/A'
|
|
352
|
-
}))
|
|
353
|
-
|
|
354
|
-
const totalOrders = rawData.length || 0
|
|
355
|
-
const totalRevenue = rawData.reduce((sum: number, t: any) => sum + t.total_amount, 0)
|
|
356
|
-
const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0
|
|
357
|
-
|
|
358
|
-
// Mock data for ecommerce metrics
|
|
359
|
-
const ecommerceMetrics = [
|
|
360
|
-
{
|
|
361
|
-
title: 'Total Revenue',
|
|
362
|
-
value: `$${totalRevenue.toLocaleString()}`,
|
|
363
|
-
change: '+12.5%',
|
|
364
|
-
trend: 'up',
|
|
365
|
-
icon: DollarSign,
|
|
366
|
-
color: 'text-green-600'
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
title: 'Orders',
|
|
370
|
-
value: totalOrders.toLocaleString(),
|
|
371
|
-
change: '+8.2%',
|
|
372
|
-
trend: 'up',
|
|
373
|
-
icon: ShoppingCart,
|
|
374
|
-
color: 'text-blue-600'
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
title: 'Average Order Value',
|
|
378
|
-
value: `$${averageOrderValue.toFixed(2)}`,
|
|
379
|
-
change: '+3.1%',
|
|
380
|
-
trend: 'up',
|
|
381
|
-
icon: TrendingUp,
|
|
382
|
-
color: 'text-purple-600'
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
title: 'Total Customers',
|
|
386
|
-
value: '2,847',
|
|
387
|
-
change: '+15.3%',
|
|
388
|
-
trend: 'up',
|
|
389
|
-
icon: Users,
|
|
390
|
-
color: 'text-orange-600'
|
|
391
|
-
}
|
|
392
|
-
]
|
|
393
|
-
|
|
394
|
-
return (
|
|
395
|
-
<div className="space-y-6">
|
|
396
|
-
{/* API Status */}
|
|
397
|
-
<div className="flex items-center justify-between">
|
|
398
|
-
<div className="flex items-center space-x-4">
|
|
399
|
-
{renderQueryState(metricQuery, 'Metrics API')}
|
|
400
|
-
{renderQueryState(datasetQuery, 'Dataset API')}
|
|
401
|
-
{renderQueryState(userQuery, 'User API')}
|
|
402
|
-
</div>
|
|
403
|
-
<Button
|
|
404
|
-
variant="outline"
|
|
405
|
-
size="sm"
|
|
406
|
-
onClick={() => {
|
|
407
|
-
metricQuery.refetch()
|
|
408
|
-
datasetQuery.refetch()
|
|
409
|
-
userQuery.refetch()
|
|
410
|
-
}}
|
|
411
|
-
disabled={metricQuery.isLoading || datasetQuery.isLoading || userQuery.isLoading ||
|
|
412
|
-
metricQuery.isFetching || datasetQuery.isFetching || userQuery.isFetching}
|
|
413
|
-
>
|
|
414
|
-
{(metricQuery.isFetching || datasetQuery.isFetching || userQuery.isFetching) ? (
|
|
415
|
-
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
416
|
-
) : (
|
|
417
|
-
<RefreshCw className="h-4 w-4 mr-2" />
|
|
418
|
-
)}
|
|
419
|
-
Refresh Data
|
|
420
|
-
</Button>
|
|
421
|
-
</div>
|
|
422
|
-
|
|
423
|
-
{/* Key Metrics */}
|
|
424
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
425
|
-
{ecommerceMetrics.map((metric, index) => (
|
|
426
|
-
<Card key={index}>
|
|
427
|
-
<CardContent className="pt-6">
|
|
428
|
-
<div className="flex items-center justify-between space-y-0 pb-2">
|
|
429
|
-
<CardTitle className="text-sm font-medium text-gray-600">
|
|
430
|
-
{metric.title}
|
|
431
|
-
</CardTitle>
|
|
432
|
-
<metric.icon className={`h-4 w-4 ${metric.color}`} />
|
|
433
|
-
</div>
|
|
434
|
-
<div className="flex items-center space-x-2">
|
|
435
|
-
<div className="text-2xl font-bold">{metric.value}</div>
|
|
436
|
-
<div className={`flex items-center text-sm ${
|
|
437
|
-
metric.trend === 'up' ? 'text-green-600' : 'text-red-600'
|
|
438
|
-
}`}>
|
|
439
|
-
{metric.trend === 'up' ? (
|
|
440
|
-
<ArrowUpRight className="h-4 w-4" />
|
|
441
|
-
) : (
|
|
442
|
-
<ArrowDownRight className="h-4 w-4" />
|
|
443
|
-
)}
|
|
444
|
-
{metric.change}
|
|
445
|
-
</div>
|
|
446
|
-
</div>
|
|
447
|
-
{/* Mini trend chart */}
|
|
448
|
-
{monthlyRevenueData.length > 0 && (
|
|
449
|
-
<div className="mt-4">
|
|
450
|
-
<SparkAreaChart
|
|
451
|
-
data={monthlyRevenueData.slice(-6)} // Last 6 months
|
|
452
|
-
categories={[index === 0 ? "revenue" : "orders"]}
|
|
453
|
-
index="month"
|
|
454
|
-
colors={[index === 0 ? "emerald" : index === 1 ? "blue" : index === 2 ? "violet" : "amber"]}
|
|
455
|
-
className="h-10 w-full"
|
|
456
|
-
/>
|
|
457
|
-
</div>
|
|
458
|
-
)}
|
|
459
|
-
</CardContent>
|
|
460
|
-
</Card>
|
|
461
|
-
))}
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
{/* Sales Analytics Row */}
|
|
465
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
466
|
-
{/* Enhanced Sales by Category */}
|
|
467
|
-
<Card>
|
|
468
|
-
<CardHeader className="pb-4">
|
|
469
|
-
<div className="flex items-center justify-between">
|
|
470
|
-
<div>
|
|
471
|
-
<CardTitle className="text-lg font-bold">Sales by Category</CardTitle>
|
|
472
|
-
<CardDescription className="text-sm text-gray-600">
|
|
473
|
-
Performance breakdown by product categories
|
|
474
|
-
</CardDescription>
|
|
475
|
-
</div>
|
|
476
|
-
<Badge variant="outline" className="text-emerald-600 border-emerald-200">
|
|
477
|
-
{orderData.length} Categories
|
|
478
|
-
</Badge>
|
|
479
|
-
</div>
|
|
480
|
-
</CardHeader>
|
|
481
|
-
<CardContent>
|
|
482
|
-
{orderData.length > 0 ? (
|
|
483
|
-
<div className="space-y-4">
|
|
484
|
-
{/* Category Trend Overview */}
|
|
485
|
-
<div className="grid grid-cols-3 gap-4 p-4 bg-gradient-to-r from-emerald-50 to-blue-50 rounded-lg border border-emerald-100">
|
|
486
|
-
<div className="text-center">
|
|
487
|
-
<div className="text-xl font-bold text-emerald-700">
|
|
488
|
-
{orderData.length}
|
|
489
|
-
</div>
|
|
490
|
-
<div className="text-xs text-emerald-600 font-medium">Categories</div>
|
|
491
|
-
</div>
|
|
492
|
-
<div className="text-center">
|
|
493
|
-
<div className="text-xl font-bold text-blue-700">
|
|
494
|
-
${Math.max(...orderData.map((item: any) => item.total)).toLocaleString()}
|
|
495
|
-
</div>
|
|
496
|
-
<div className="text-xs text-blue-600 font-medium">Top Revenue</div>
|
|
497
|
-
</div>
|
|
498
|
-
<div className="text-center">
|
|
499
|
-
<div className="text-xl font-bold text-violet-700">
|
|
500
|
-
{Math.round(orderData.reduce((sum: number, item: any) => sum + item.count, 0) / orderData.length)}
|
|
501
|
-
</div>
|
|
502
|
-
<div className="text-xs text-violet-600 font-medium">Avg Orders</div>
|
|
503
|
-
</div>
|
|
504
|
-
</div>
|
|
505
|
-
|
|
506
|
-
{/* Area Chart for Category Trends */}
|
|
507
|
-
<div className="h-80">
|
|
508
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
509
|
-
<RechartsAreaChart
|
|
510
|
-
data={orderData.map((item: any, index: number) => ({
|
|
511
|
-
category: item.category,
|
|
512
|
-
revenue: item.total,
|
|
513
|
-
orders: item.count * 10, // Scale for visualization
|
|
514
|
-
trend: item.total * (1 + (index * 0.1)) // Mock trend data
|
|
515
|
-
}))}
|
|
516
|
-
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
|
|
517
|
-
>
|
|
518
|
-
<defs>
|
|
519
|
-
<linearGradient id="revenueGradient" x1="0" y1="0" x2="0" y2="1">
|
|
520
|
-
<stop offset="5%" stopColor="#10B981" stopOpacity={0.8}/>
|
|
521
|
-
<stop offset="95%" stopColor="#10B981" stopOpacity={0.1}/>
|
|
522
|
-
</linearGradient>
|
|
523
|
-
<linearGradient id="ordersGradient" x1="0" y1="0" x2="0" y2="1">
|
|
524
|
-
<stop offset="5%" stopColor="#3B82F6" stopOpacity={0.8}/>
|
|
525
|
-
<stop offset="95%" stopColor="#3B82F6" stopOpacity={0.1}/>
|
|
526
|
-
</linearGradient>
|
|
527
|
-
</defs>
|
|
528
|
-
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
|
529
|
-
<XAxis
|
|
530
|
-
dataKey="category"
|
|
531
|
-
tick={{ fontSize: 12 }}
|
|
532
|
-
angle={-45}
|
|
533
|
-
textAnchor="end"
|
|
534
|
-
height={80}
|
|
535
|
-
/>
|
|
536
|
-
<YAxis tick={{ fontSize: 12 }} />
|
|
537
|
-
<Tooltip
|
|
538
|
-
formatter={(value: any, name: string) => [
|
|
539
|
-
name === 'revenue' ? `$${value.toLocaleString()}` : `${Math.round(value / 10)} orders`,
|
|
540
|
-
name === 'revenue' ? 'Revenue' : 'Orders'
|
|
541
|
-
]}
|
|
542
|
-
labelStyle={{ color: '#374151' }}
|
|
543
|
-
contentStyle={{
|
|
544
|
-
backgroundColor: '#fff',
|
|
545
|
-
border: '1px solid #e5e7eb',
|
|
546
|
-
borderRadius: '8px',
|
|
547
|
-
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
|
|
548
|
-
}}
|
|
549
|
-
/>
|
|
550
|
-
<Legend />
|
|
551
|
-
<Area
|
|
552
|
-
type="monotone"
|
|
553
|
-
dataKey="revenue"
|
|
554
|
-
stroke="#10B981"
|
|
555
|
-
fillOpacity={1}
|
|
556
|
-
fill="url(#revenueGradient)"
|
|
557
|
-
strokeWidth={2}
|
|
558
|
-
/>
|
|
559
|
-
<Area
|
|
560
|
-
type="monotone"
|
|
561
|
-
dataKey="orders"
|
|
562
|
-
stroke="#3B82F6"
|
|
563
|
-
fillOpacity={1}
|
|
564
|
-
fill="url(#ordersGradient)"
|
|
565
|
-
strokeWidth={2}
|
|
566
|
-
/>
|
|
567
|
-
</RechartsAreaChart>
|
|
568
|
-
</ResponsiveContainer>
|
|
569
|
-
</div>
|
|
570
|
-
</div>
|
|
571
|
-
) : (
|
|
572
|
-
<div className="text-center text-gray-500 py-12">
|
|
573
|
-
<ShoppingCart className="h-12 w-12 mx-auto text-gray-300 mb-4" />
|
|
574
|
-
<p className="text-lg font-medium">No category data available</p>
|
|
575
|
-
<p className="text-sm">Start selling to see category breakdown</p>
|
|
576
|
-
</div>
|
|
577
|
-
)}
|
|
578
|
-
</CardContent>
|
|
579
|
-
</Card>
|
|
580
|
-
|
|
581
|
-
{/* Enhanced Payment Method Distribution */}
|
|
582
|
-
<Card>
|
|
583
|
-
<CardHeader className="pb-4">
|
|
584
|
-
<div className="flex items-center justify-between">
|
|
585
|
-
<div>
|
|
586
|
-
<CardTitle className="text-lg font-bold">Payment Methods</CardTitle>
|
|
587
|
-
<CardDescription className="text-sm text-gray-600">
|
|
588
|
-
Transaction distribution by payment type
|
|
589
|
-
</CardDescription>
|
|
590
|
-
</div>
|
|
591
|
-
<Badge variant="outline" className="text-blue-600 border-blue-200">
|
|
592
|
-
{paymentMethodData.length} Methods
|
|
593
|
-
</Badge>
|
|
594
|
-
</div>
|
|
595
|
-
</CardHeader>
|
|
596
|
-
<CardContent>
|
|
597
|
-
{paymentMethodData.length > 0 ? (
|
|
598
|
-
<div className="space-y-6">
|
|
599
|
-
{/* Enhanced Payment Method Donut Chart */}
|
|
600
|
-
<div className="relative bg-gradient-to-br from-blue-50 to-indigo-50 p-6 rounded-lg border border-blue-100">
|
|
601
|
-
<h3 className="text-sm font-semibold text-blue-800 mb-4 text-center">Payment Distribution</h3>
|
|
602
|
-
<div className="h-80">
|
|
603
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
604
|
-
<PieChart>
|
|
605
|
-
<defs>
|
|
606
|
-
<filter id="shadow" height="130%">
|
|
607
|
-
<feDropShadow dx="2" dy="2" stdDeviation="3" floodOpacity="0.2"/>
|
|
608
|
-
</filter>
|
|
609
|
-
</defs>
|
|
610
|
-
<Pie
|
|
611
|
-
data={paymentMethodData.map((item: any) => ({
|
|
612
|
-
name: item.method,
|
|
613
|
-
value: item.total,
|
|
614
|
-
count: item.count,
|
|
615
|
-
percentage: Math.round((item.total / paymentMethodData.reduce((sum: number, m: any) => sum + m.total, 0)) * 100)
|
|
616
|
-
}))}
|
|
617
|
-
cx="50%"
|
|
618
|
-
cy="50%"
|
|
619
|
-
labelLine={false}
|
|
620
|
-
label={({ name, percentage }: any) => `${name}: ${percentage}%`}
|
|
621
|
-
outerRadius={100}
|
|
622
|
-
innerRadius={60}
|
|
623
|
-
paddingAngle={5}
|
|
624
|
-
dataKey="value"
|
|
625
|
-
filter="url(#shadow)"
|
|
626
|
-
>
|
|
627
|
-
{paymentMethodData.map((_entry: any, index: number) => {
|
|
628
|
-
const colors = ['#3B82F6', '#10B981', '#8B5CF6', '#F59E0B', '#F43F5E', '#06B6D4']
|
|
629
|
-
return (
|
|
630
|
-
<Cell
|
|
631
|
-
key={`cell-${index}`}
|
|
632
|
-
fill={colors[index % colors.length]}
|
|
633
|
-
stroke="#fff"
|
|
634
|
-
strokeWidth={2}
|
|
635
|
-
/>
|
|
636
|
-
)
|
|
637
|
-
})}
|
|
638
|
-
</Pie>
|
|
639
|
-
<Tooltip
|
|
640
|
-
formatter={(value: any, name: string, props: any) => [
|
|
641
|
-
`$${value.toLocaleString()}`,
|
|
642
|
-
`${name} (${props.payload.count} transactions)`
|
|
643
|
-
]}
|
|
644
|
-
labelStyle={{ color: '#374151', fontWeight: 'bold' }}
|
|
645
|
-
contentStyle={{
|
|
646
|
-
backgroundColor: '#fff',
|
|
647
|
-
border: '1px solid #e5e7eb',
|
|
648
|
-
borderRadius: '8px',
|
|
649
|
-
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
|
|
650
|
-
}}
|
|
651
|
-
/>
|
|
652
|
-
<Legend
|
|
653
|
-
verticalAlign="bottom"
|
|
654
|
-
height={36}
|
|
655
|
-
iconType="circle"
|
|
656
|
-
wrapperStyle={{ fontSize: '12px', paddingTop: '20px' }}
|
|
657
|
-
/>
|
|
658
|
-
</PieChart>
|
|
659
|
-
</ResponsiveContainer>
|
|
660
|
-
</div>
|
|
661
|
-
</div>
|
|
662
|
-
|
|
663
|
-
{/* Enhanced Payment Method Details */}
|
|
664
|
-
<div className="space-y-3">
|
|
665
|
-
<h3 className="text-sm font-semibold text-gray-700">Payment Method Breakdown</h3>
|
|
666
|
-
{paymentMethodData.map((method: any, index: number) => {
|
|
667
|
-
const colors = ['bg-blue-500', 'bg-emerald-500', 'bg-violet-500', 'bg-amber-500', 'bg-rose-500', 'bg-cyan-500']
|
|
668
|
-
const textColors = ['text-blue-600', 'text-emerald-600', 'text-violet-600', 'text-amber-600', 'text-rose-600', 'text-cyan-600']
|
|
669
|
-
const bgColors = ['bg-blue-50', 'bg-emerald-50', 'bg-violet-50', 'bg-amber-50', 'bg-rose-50', 'bg-cyan-50']
|
|
670
|
-
const percentage = Math.round((method.total / paymentMethodData.reduce((sum: number, m: any) => sum + m.total, 0)) * 100)
|
|
671
|
-
|
|
672
|
-
return (
|
|
673
|
-
<div key={index} className={`flex items-center justify-between p-4 rounded-lg border hover:shadow-sm transition-all ${bgColors[index] || 'bg-gray-50'}`}>
|
|
674
|
-
<div className="flex items-center space-x-4">
|
|
675
|
-
<div className={`w-4 h-4 rounded-full ${colors[index] || 'bg-gray-400'}`} />
|
|
676
|
-
<div>
|
|
677
|
-
<div className="font-semibold text-gray-900">{method.method}</div>
|
|
678
|
-
<div className="text-sm text-gray-500">{method.count} transactions</div>
|
|
679
|
-
</div>
|
|
680
|
-
</div>
|
|
681
|
-
<div className="text-right">
|
|
682
|
-
<div className={`text-lg font-bold ${textColors[index] || 'text-gray-600'}`}>
|
|
683
|
-
${method.total.toLocaleString()}
|
|
684
|
-
</div>
|
|
685
|
-
<div className="text-sm text-gray-500">{percentage}% of total</div>
|
|
686
|
-
<div className="w-20 bg-gray-200 rounded-full h-1.5 mt-1">
|
|
687
|
-
<div
|
|
688
|
-
className={`h-1.5 rounded-full ${colors[index] || 'bg-gray-400'}`}
|
|
689
|
-
style={{ width: `${percentage}%` }}
|
|
690
|
-
/>
|
|
691
|
-
</div>
|
|
692
|
-
</div>
|
|
693
|
-
</div>
|
|
694
|
-
)
|
|
695
|
-
})}
|
|
696
|
-
</div>
|
|
697
|
-
</div>
|
|
698
|
-
) : (
|
|
699
|
-
<div className="text-center text-gray-500 py-12">
|
|
700
|
-
<DollarSign className="h-12 w-12 mx-auto text-gray-300 mb-4" />
|
|
701
|
-
<p className="text-lg font-medium">No payment data available</p>
|
|
702
|
-
<p className="text-sm">Process payments to see distribution</p>
|
|
703
|
-
</div>
|
|
704
|
-
)}
|
|
705
|
-
</CardContent>
|
|
706
|
-
</Card>
|
|
707
|
-
</div>
|
|
708
|
-
|
|
709
|
-
{/* Enhanced Recent Transactions Table */}
|
|
710
|
-
<Card>
|
|
711
|
-
<CardHeader>
|
|
712
|
-
<div className="flex items-center justify-between">
|
|
713
|
-
<div>
|
|
714
|
-
<CardTitle className="text-lg font-bold flex items-center gap-2">
|
|
715
|
-
📋 Recent Transactions
|
|
716
|
-
</CardTitle>
|
|
717
|
-
<CardDescription>
|
|
718
|
-
Latest customer transactions with comprehensive details and analytics
|
|
719
|
-
</CardDescription>
|
|
720
|
-
</div>
|
|
721
|
-
<div className="flex items-center space-x-2">
|
|
722
|
-
<Badge variant="outline" className="text-blue-600 border-blue-200">
|
|
723
|
-
{recentTransactions.length} Recent
|
|
724
|
-
</Badge>
|
|
725
|
-
<Badge variant="outline" className="text-emerald-600 border-emerald-200">
|
|
726
|
-
${recentTransactions.reduce((sum: number, t: any) => sum + t.amount, 0).toLocaleString()} Total
|
|
727
|
-
</Badge>
|
|
728
|
-
</div>
|
|
729
|
-
</div>
|
|
730
|
-
</CardHeader>
|
|
731
|
-
<CardContent>
|
|
732
|
-
{recentTransactions.length > 0 ? (
|
|
733
|
-
<div className="space-y-4">
|
|
734
|
-
{/* Transaction Summary Cards */}
|
|
735
|
-
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 p-4 bg-gradient-to-r from-gray-50 to-slate-50 rounded-lg border border-gray-100">
|
|
736
|
-
<div className="text-center">
|
|
737
|
-
<div className="text-xl font-bold text-gray-700">
|
|
738
|
-
{recentTransactions.length}
|
|
739
|
-
</div>
|
|
740
|
-
<div className="text-xs text-gray-600 font-medium">Total Transactions</div>
|
|
741
|
-
</div>
|
|
742
|
-
<div className="text-center">
|
|
743
|
-
<div className="text-xl font-bold text-emerald-700">
|
|
744
|
-
${Math.round(recentTransactions.reduce((sum: number, t: any) => sum + t.amount, 0) / recentTransactions.length).toLocaleString()}
|
|
745
|
-
</div>
|
|
746
|
-
<div className="text-xs text-emerald-600 font-medium">Average Value</div>
|
|
747
|
-
</div>
|
|
748
|
-
<div className="text-center">
|
|
749
|
-
<div className="text-xl font-bold text-blue-700">
|
|
750
|
-
${Math.max(...recentTransactions.map((t: any) => t.amount)).toLocaleString()}
|
|
751
|
-
</div>
|
|
752
|
-
<div className="text-xs text-blue-600 font-medium">Highest Transaction</div>
|
|
753
|
-
</div>
|
|
754
|
-
<div className="text-center">
|
|
755
|
-
<div className="text-xl font-bold text-violet-700">
|
|
756
|
-
{new Set(recentTransactions.map((t: any) => t.category)).size}
|
|
757
|
-
</div>
|
|
758
|
-
<div className="text-xs text-violet-600 font-medium">Categories</div>
|
|
759
|
-
</div>
|
|
760
|
-
</div>
|
|
761
|
-
|
|
762
|
-
{/* Enhanced Data Table */}
|
|
763
|
-
<div className="overflow-hidden rounded-lg border border-gray-200 shadow-sm">
|
|
764
|
-
<Table className="min-w-full">
|
|
765
|
-
<TableHead>
|
|
766
|
-
<TableRow>
|
|
767
|
-
<TableHeaderCell>Transaction ID</TableHeaderCell>
|
|
768
|
-
<TableHeaderCell>Customer</TableHeaderCell>
|
|
769
|
-
<TableHeaderCell>Category</TableHeaderCell>
|
|
770
|
-
<TableHeaderCell>Amount</TableHeaderCell>
|
|
771
|
-
<TableHeaderCell>Payment Method</TableHeaderCell>
|
|
772
|
-
<TableHeaderCell>Date</TableHeaderCell>
|
|
773
|
-
<TableHeaderCell>Status</TableHeaderCell>
|
|
774
|
-
</TableRow>
|
|
775
|
-
</TableHead>
|
|
776
|
-
<TableBody>
|
|
777
|
-
{recentTransactions.map((transaction: any) => (
|
|
778
|
-
<TableRow key={transaction.id}>
|
|
779
|
-
<TableCell className="font-mono text-sm font-medium text-gray-900">
|
|
780
|
-
#{transaction.id}
|
|
781
|
-
</TableCell>
|
|
782
|
-
<TableCell className="font-medium text-gray-900">
|
|
783
|
-
<div className="flex items-center space-x-2">
|
|
784
|
-
<div className="w-8 h-8 bg-gradient-to-br from-blue-400 to-blue-600 rounded-full flex items-center justify-center text-white text-xs font-bold">
|
|
785
|
-
{transaction.customer.split(' ').map((n: string) => n[0]).join('')}
|
|
786
|
-
</div>
|
|
787
|
-
<span>{transaction.customer}</span>
|
|
788
|
-
</div>
|
|
789
|
-
</TableCell>
|
|
790
|
-
<TableCell>
|
|
791
|
-
<Badge
|
|
792
|
-
variant="outline"
|
|
793
|
-
className={`text-xs ${
|
|
794
|
-
transaction.category === 'Electronics' ? 'text-blue-600 border-blue-200 bg-blue-50' :
|
|
795
|
-
transaction.category === 'Clothing' ? 'text-emerald-600 border-emerald-200 bg-emerald-50' :
|
|
796
|
-
transaction.category === 'Sports' ? 'text-violet-600 border-violet-200 bg-violet-50' :
|
|
797
|
-
transaction.category === 'Books' ? 'text-amber-600 border-amber-200 bg-amber-50' :
|
|
798
|
-
'text-rose-600 border-rose-200 bg-rose-50'
|
|
799
|
-
}`}
|
|
800
|
-
>
|
|
801
|
-
{transaction.category}
|
|
802
|
-
</Badge>
|
|
803
|
-
</TableCell>
|
|
804
|
-
<TableCell className="font-bold text-right">
|
|
805
|
-
<div className={`text-lg ${
|
|
806
|
-
transaction.amount > 1500 ? 'text-emerald-600' :
|
|
807
|
-
transaction.amount > 1000 ? 'text-blue-600' :
|
|
808
|
-
transaction.amount > 500 ? 'text-amber-600' :
|
|
809
|
-
'text-gray-600'
|
|
810
|
-
}`}>
|
|
811
|
-
${transaction.amount.toLocaleString()}
|
|
812
|
-
</div>
|
|
813
|
-
</TableCell>
|
|
814
|
-
<TableCell>
|
|
815
|
-
<div className="flex items-center space-x-2">
|
|
816
|
-
<div className={`w-2 h-2 rounded-full ${
|
|
817
|
-
transaction.payment_method === 'Credit Card' ? 'bg-blue-500' :
|
|
818
|
-
transaction.payment_method === 'PayPal' ? 'bg-yellow-500' :
|
|
819
|
-
transaction.payment_method === 'Debit Card' ? 'bg-emerald-500' :
|
|
820
|
-
'bg-gray-500'
|
|
821
|
-
}`} />
|
|
822
|
-
<span className="text-sm">{transaction.payment_method}</span>
|
|
823
|
-
</div>
|
|
824
|
-
</TableCell>
|
|
825
|
-
<TableCell className="text-gray-500 text-sm">
|
|
826
|
-
{transaction.date}
|
|
827
|
-
</TableCell>
|
|
828
|
-
<TableCell>
|
|
829
|
-
<Badge
|
|
830
|
-
variant="outline"
|
|
831
|
-
className="text-xs text-emerald-600 border-emerald-200 bg-emerald-50"
|
|
832
|
-
>
|
|
833
|
-
Completed
|
|
834
|
-
</Badge>
|
|
835
|
-
</TableCell>
|
|
836
|
-
</TableRow>
|
|
837
|
-
))}
|
|
838
|
-
</TableBody>
|
|
839
|
-
</Table>
|
|
840
|
-
</div>
|
|
841
|
-
|
|
842
|
-
{/* Table Footer with Pagination Info */}
|
|
843
|
-
<div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg border border-gray-100">
|
|
844
|
-
<div className="text-sm text-gray-600">
|
|
845
|
-
Showing <span className="font-medium">{recentTransactions.length}</span> of <span className="font-medium">{totalOrders}</span> transactions
|
|
846
|
-
</div>
|
|
847
|
-
<div className="text-sm text-gray-600">
|
|
848
|
-
Total Value: <span className="font-bold text-emerald-600">${recentTransactions.reduce((sum: number, t: any) => sum + t.amount, 0).toLocaleString()}</span>
|
|
849
|
-
</div>
|
|
850
|
-
</div>
|
|
851
|
-
</div>
|
|
852
|
-
) : (
|
|
853
|
-
<div className="text-center text-gray-500 py-12">
|
|
854
|
-
<div className="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
|
|
855
|
-
📊
|
|
856
|
-
</div>
|
|
857
|
-
<p className="text-lg font-medium">No transaction data available</p>
|
|
858
|
-
<p className="text-sm">Start processing transactions to see them here</p>
|
|
859
|
-
</div>
|
|
860
|
-
)}
|
|
861
|
-
</CardContent>
|
|
862
|
-
</Card>
|
|
863
|
-
|
|
864
|
-
{/* Enhanced Sales Analytics */}
|
|
865
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
866
|
-
{/* Top Categories Performance */}
|
|
867
|
-
<Card>
|
|
868
|
-
<CardHeader>
|
|
869
|
-
<CardTitle>Top Performing Categories</CardTitle>
|
|
870
|
-
<CardDescription>
|
|
871
|
-
Revenue performance by category
|
|
872
|
-
</CardDescription>
|
|
873
|
-
</CardHeader>
|
|
874
|
-
<CardContent>
|
|
875
|
-
{orderData.length > 0 ? (
|
|
876
|
-
<div className="space-y-4">
|
|
877
|
-
{orderData.slice(0, 5).map((category: any, index: number) => {
|
|
878
|
-
const percentage = Math.round((category.total / orderData.reduce((sum: number, cat: any) => sum + cat.total, 0)) * 100)
|
|
879
|
-
return (
|
|
880
|
-
<div key={index} className="space-y-2">
|
|
881
|
-
<div className="flex items-center justify-between">
|
|
882
|
-
<div className="flex items-center space-x-2">
|
|
883
|
-
<div className={`w-3 h-3 rounded-full ${
|
|
884
|
-
index === 0 ? 'bg-emerald-500' :
|
|
885
|
-
index === 1 ? 'bg-blue-500' :
|
|
886
|
-
index === 2 ? 'bg-violet-500' :
|
|
887
|
-
index === 3 ? 'bg-amber-500' : 'bg-rose-500'
|
|
888
|
-
}`} />
|
|
889
|
-
<span className="text-sm font-medium">{category.category}</span>
|
|
890
|
-
</div>
|
|
891
|
-
<div className="text-right">
|
|
892
|
-
<div className="text-sm font-bold">${category.total.toLocaleString()}</div>
|
|
893
|
-
<div className="text-xs text-gray-500">{percentage}%</div>
|
|
894
|
-
</div>
|
|
895
|
-
</div>
|
|
896
|
-
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
897
|
-
<div
|
|
898
|
-
className={`h-2 rounded-full ${
|
|
899
|
-
index === 0 ? 'bg-emerald-500' :
|
|
900
|
-
index === 1 ? 'bg-blue-500' :
|
|
901
|
-
index === 2 ? 'bg-violet-500' :
|
|
902
|
-
index === 3 ? 'bg-amber-500' : 'bg-rose-500'
|
|
903
|
-
}`}
|
|
904
|
-
style={{ width: `${percentage}%` }}
|
|
905
|
-
/>
|
|
906
|
-
</div>
|
|
907
|
-
</div>
|
|
908
|
-
)
|
|
909
|
-
})}
|
|
910
|
-
</div>
|
|
911
|
-
) : (
|
|
912
|
-
<div className="text-center text-gray-500 py-8">
|
|
913
|
-
No category data available
|
|
914
|
-
</div>
|
|
915
|
-
)}
|
|
916
|
-
</CardContent>
|
|
917
|
-
</Card>
|
|
918
|
-
|
|
919
|
-
{/* Sales Insights */}
|
|
920
|
-
<Card>
|
|
921
|
-
<CardHeader>
|
|
922
|
-
<CardTitle>Sales Insights</CardTitle>
|
|
923
|
-
<CardDescription>
|
|
924
|
-
Key performance indicators
|
|
925
|
-
</CardDescription>
|
|
926
|
-
</CardHeader>
|
|
927
|
-
<CardContent>
|
|
928
|
-
<div className="space-y-6">
|
|
929
|
-
<div className="text-center">
|
|
930
|
-
<div className="text-3xl font-bold text-emerald-600">
|
|
931
|
-
${averageOrderValue.toFixed(2)}
|
|
932
|
-
</div>
|
|
933
|
-
<div className="text-sm text-gray-500">Average Order Value</div>
|
|
934
|
-
<div className="mt-2">
|
|
935
|
-
<Badge variant="outline" className="text-green-600 border-green-200">
|
|
936
|
-
+5.2% vs last month
|
|
937
|
-
</Badge>
|
|
938
|
-
</div>
|
|
939
|
-
</div>
|
|
940
|
-
|
|
941
|
-
<div className="text-center">
|
|
942
|
-
<div className="text-3xl font-bold text-blue-600">
|
|
943
|
-
{totalOrders > 0 ? Math.round(totalRevenue / totalOrders * 12) : 0}%
|
|
944
|
-
</div>
|
|
945
|
-
<div className="text-sm text-gray-500">Conversion Rate</div>
|
|
946
|
-
<div className="mt-2">
|
|
947
|
-
<Badge variant="outline" className="text-blue-600 border-blue-200">
|
|
948
|
-
Industry average: 2.3%
|
|
949
|
-
</Badge>
|
|
950
|
-
</div>
|
|
951
|
-
</div>
|
|
952
|
-
|
|
953
|
-
<div className="text-center">
|
|
954
|
-
<div className="text-3xl font-bold text-violet-600">
|
|
955
|
-
{paymentMethodData.length}
|
|
956
|
-
</div>
|
|
957
|
-
<div className="text-sm text-gray-500">Payment Methods</div>
|
|
958
|
-
<div className="mt-2">
|
|
959
|
-
<Badge variant="outline" className="text-violet-600 border-violet-200">
|
|
960
|
-
Active channels
|
|
961
|
-
</Badge>
|
|
962
|
-
</div>
|
|
963
|
-
</div>
|
|
964
|
-
</div>
|
|
965
|
-
</CardContent>
|
|
966
|
-
</Card>
|
|
967
|
-
|
|
968
|
-
{/* Recent Activity */}
|
|
969
|
-
<Card>
|
|
970
|
-
<CardHeader>
|
|
971
|
-
<CardTitle>Recent Activity</CardTitle>
|
|
972
|
-
<CardDescription>
|
|
973
|
-
Latest updates and activities
|
|
974
|
-
</CardDescription>
|
|
975
|
-
</CardHeader>
|
|
976
|
-
<CardContent>
|
|
977
|
-
<div className="space-y-4">
|
|
978
|
-
<div className="flex items-center space-x-4 p-3 bg-emerald-50 rounded-lg border border-emerald-100">
|
|
979
|
-
<div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse" />
|
|
980
|
-
<div className="flex-1">
|
|
981
|
-
<p className="text-sm font-medium text-emerald-800">High Revenue Day</p>
|
|
982
|
-
<p className="text-xs text-emerald-600">Revenue exceeded daily target by 15%</p>
|
|
983
|
-
</div>
|
|
984
|
-
<Badge variant="outline" className="text-emerald-600 border-emerald-200">Live</Badge>
|
|
985
|
-
</div>
|
|
986
|
-
|
|
987
|
-
<div className="flex items-center space-x-4 p-3 bg-blue-50 rounded-lg border border-blue-100">
|
|
988
|
-
<ShoppingCart className="h-5 w-5 text-blue-600" />
|
|
989
|
-
<div className="flex-1">
|
|
990
|
-
<p className="text-sm font-medium text-blue-800">New Order Received</p>
|
|
991
|
-
<p className="text-xs text-blue-600">Order #ORD-{Date.now().toString().slice(-4)} - ${(Math.random() * 500 + 50).toFixed(2)}</p>
|
|
992
|
-
</div>
|
|
993
|
-
<Badge variant="outline" className="text-blue-600 border-blue-200">New</Badge>
|
|
994
|
-
</div>
|
|
995
|
-
|
|
996
|
-
<div className="flex items-center space-x-4 p-3 bg-violet-50 rounded-lg border border-violet-100">
|
|
997
|
-
<TrendingUp className="h-5 w-5 text-violet-600" />
|
|
998
|
-
<div className="flex-1">
|
|
999
|
-
<p className="text-sm font-medium text-violet-800">Sales Trending Up</p>
|
|
1000
|
-
<p className="text-xs text-violet-600">12% increase in last 24 hours</p>
|
|
1001
|
-
</div>
|
|
1002
|
-
<Badge variant="outline" className="text-violet-600 border-violet-200">Trend</Badge>
|
|
1003
|
-
</div>
|
|
1004
|
-
|
|
1005
|
-
<div className="flex items-center space-x-4 p-3 bg-amber-50 rounded-lg border border-amber-100">
|
|
1006
|
-
<Users className="h-5 w-5 text-amber-600" />
|
|
1007
|
-
<div className="flex-1">
|
|
1008
|
-
<p className="text-sm font-medium text-amber-800">Customer Milestone</p>
|
|
1009
|
-
<p className="text-xs text-amber-600">Reached 1,000+ customers this month</p>
|
|
1010
|
-
</div>
|
|
1011
|
-
<Badge variant="outline" className="text-amber-600 border-amber-200">Achievement</Badge>
|
|
1012
|
-
</div>
|
|
1013
|
-
</div>
|
|
1014
|
-
</CardContent>
|
|
1015
|
-
</Card>
|
|
1016
|
-
</div>
|
|
1017
|
-
|
|
1018
|
-
{/* Enhanced Revenue and Analytics Section */}
|
|
1019
|
-
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
|
1020
|
-
{/* Sales by Category Chart */}
|
|
1021
|
-
<Card>
|
|
1022
|
-
<CardHeader>
|
|
1023
|
-
<CardTitle>Sales by Category</CardTitle>
|
|
1024
|
-
<CardDescription>
|
|
1025
|
-
Revenue breakdown by product category
|
|
1026
|
-
</CardDescription>
|
|
1027
|
-
</CardHeader>
|
|
1028
|
-
<CardContent>
|
|
1029
|
-
<DonutChart
|
|
1030
|
-
className="h-64"
|
|
1031
|
-
data={orderData.map((item: any) => ({
|
|
1032
|
-
category: item.category,
|
|
1033
|
-
sales: item.total
|
|
1034
|
-
}))}
|
|
1035
|
-
category="sales"
|
|
1036
|
-
index="category"
|
|
1037
|
-
valueFormatter={(number: number) =>
|
|
1038
|
-
`$${Intl.NumberFormat("us").format(number)}`
|
|
1039
|
-
}
|
|
1040
|
-
colors={["slate", "violet", "indigo", "rose", "cyan", "amber"]}
|
|
1041
|
-
/>
|
|
1042
|
-
</CardContent>
|
|
1043
|
-
</Card>
|
|
1044
|
-
|
|
1045
|
-
{/* Payment Method Distribution */}
|
|
1046
|
-
<Card>
|
|
1047
|
-
<CardHeader>
|
|
1048
|
-
<CardTitle>Payment Method Distribution</CardTitle>
|
|
1049
|
-
<CardDescription>
|
|
1050
|
-
Transaction count by payment method
|
|
1051
|
-
</CardDescription>
|
|
1052
|
-
</CardHeader>
|
|
1053
|
-
<CardContent>
|
|
1054
|
-
<DonutChart
|
|
1055
|
-
className="h-64"
|
|
1056
|
-
data={paymentMethodData.map((item: any) => ({
|
|
1057
|
-
method: item.method,
|
|
1058
|
-
count: item.count
|
|
1059
|
-
}))}
|
|
1060
|
-
category="count"
|
|
1061
|
-
index="method"
|
|
1062
|
-
valueFormatter={(number: number) =>
|
|
1063
|
-
`${number} transactions`
|
|
1064
|
-
}
|
|
1065
|
-
colors={["emerald", "blue", "violet", "rose", "amber"]}
|
|
1066
|
-
/>
|
|
1067
|
-
</CardContent>
|
|
1068
|
-
</Card>
|
|
1069
|
-
|
|
1070
|
-
{/* Revenue Analytics Summary */}
|
|
1071
|
-
<Card>
|
|
1072
|
-
<CardHeader>
|
|
1073
|
-
<CardTitle>Revenue Analytics</CardTitle>
|
|
1074
|
-
<CardDescription>
|
|
1075
|
-
Key performance indicators
|
|
1076
|
-
</CardDescription>
|
|
1077
|
-
</CardHeader>
|
|
1078
|
-
<CardContent>
|
|
1079
|
-
<div className="space-y-4">
|
|
1080
|
-
{/* Top Category */}
|
|
1081
|
-
<div className="p-4 bg-gradient-to-r from-emerald-50 to-blue-50 rounded-lg border border-emerald-100">
|
|
1082
|
-
<div className="text-sm text-emerald-600 font-medium">Top Revenue Category</div>
|
|
1083
|
-
<div className="text-xl font-bold text-emerald-800">
|
|
1084
|
-
{orderData.length > 0 ? orderData.sort((a: any, b: any) => b.total - a.total)[0]?.category || 'N/A' : 'Electronics'}
|
|
1085
|
-
</div>
|
|
1086
|
-
<div className="text-xs text-emerald-600">
|
|
1087
|
-
${orderData.length > 0 ? orderData.sort((a: any, b: any) => b.total - a.total)[0]?.total?.toLocaleString() || '0' : '8,750'}
|
|
1088
|
-
</div>
|
|
1089
|
-
</div>
|
|
1090
|
-
|
|
1091
|
-
{/* Average Order Value */}
|
|
1092
|
-
<div className="p-4 bg-gradient-to-r from-blue-50 to-violet-50 rounded-lg border border-blue-100">
|
|
1093
|
-
<div className="text-sm text-blue-600 font-medium">Average Order Value</div>
|
|
1094
|
-
<div className="text-xl font-bold text-blue-800">
|
|
1095
|
-
${averageOrderValue.toFixed(0)}
|
|
1096
|
-
</div>
|
|
1097
|
-
<div className="text-xs text-blue-600">
|
|
1098
|
-
+12% vs last month
|
|
1099
|
-
</div>
|
|
1100
|
-
</div>
|
|
1101
|
-
|
|
1102
|
-
{/* Total Revenue */}
|
|
1103
|
-
<div className="p-4 bg-gradient-to-r from-violet-50 to-purple-50 rounded-lg border border-violet-100">
|
|
1104
|
-
<div className="text-sm text-violet-600 font-medium">Total Revenue</div>
|
|
1105
|
-
<div className="text-xl font-bold text-violet-800">
|
|
1106
|
-
${(totalRevenue / 1000).toFixed(1)}K
|
|
1107
|
-
</div>
|
|
1108
|
-
<div className="text-xs text-violet-600">
|
|
1109
|
-
{monthlyRevenueData.length} active months
|
|
1110
|
-
</div>
|
|
1111
|
-
</div>
|
|
1112
|
-
|
|
1113
|
-
{/* Revenue Growth Trend */}
|
|
1114
|
-
<div className="p-4 bg-gradient-to-r from-amber-50 to-orange-50 rounded-lg border border-amber-100">
|
|
1115
|
-
<div className="text-sm text-amber-600 font-medium">Growth Trend</div>
|
|
1116
|
-
<div className="text-xl font-bold text-amber-800">
|
|
1117
|
-
+{totalRevenue > 0 ? ((totalRevenue / 50000) * 100).toFixed(1) : 15.3}%
|
|
1118
|
-
</div>
|
|
1119
|
-
<div className="mt-2">
|
|
1120
|
-
<SparkAreaChart
|
|
1121
|
-
data={monthlyRevenueData.slice(-6)}
|
|
1122
|
-
categories={["revenue"]}
|
|
1123
|
-
index="month"
|
|
1124
|
-
colors={["amber"]}
|
|
1125
|
-
className="h-6 w-full"
|
|
1126
|
-
/>
|
|
1127
|
-
</div>
|
|
1128
|
-
</div>
|
|
1129
|
-
</div>
|
|
1130
|
-
</CardContent>
|
|
1131
|
-
</Card>
|
|
1132
|
-
</div>
|
|
1133
|
-
|
|
1134
|
-
{/* Top Performing Categories Analytics */}
|
|
1135
|
-
<Card>
|
|
1136
|
-
<CardHeader>
|
|
1137
|
-
<div className="flex items-center justify-between">
|
|
1138
|
-
<div>
|
|
1139
|
-
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
|
1140
|
-
<TrendingUp className="h-5 w-5 text-emerald-600" />
|
|
1141
|
-
Top Performing Categories
|
|
1142
|
-
</CardTitle>
|
|
1143
|
-
<CardDescription className="text-sm text-gray-600 mt-1">
|
|
1144
|
-
Comprehensive category performance analysis with revenue and order metrics
|
|
1145
|
-
</CardDescription>
|
|
1146
|
-
</div>
|
|
1147
|
-
<Badge variant="outline" className="text-emerald-600 border-emerald-200">
|
|
1148
|
-
{orderData.length} Active Categories
|
|
1149
|
-
</Badge>
|
|
1150
|
-
</div>
|
|
1151
|
-
</CardHeader>
|
|
1152
|
-
<CardContent>
|
|
1153
|
-
{orderData.length > 0 ? (
|
|
1154
|
-
<div className="space-y-6">
|
|
1155
|
-
{/* Category Performance Overview */}
|
|
1156
|
-
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
1157
|
-
{orderData.slice(0, 4).map((category: any, index: number) => {
|
|
1158
|
-
const colors = ['bg-emerald-50 text-emerald-700 border-emerald-200', 'bg-blue-50 text-blue-700 border-blue-200',
|
|
1159
|
-
'bg-violet-50 text-violet-700 border-violet-200', 'bg-amber-50 text-amber-700 border-amber-200']
|
|
1160
|
-
const iconColors = ['text-emerald-500', 'text-blue-500', 'text-violet-500', 'text-amber-500']
|
|
1161
|
-
|
|
1162
|
-
return (
|
|
1163
|
-
<div key={index} className={`p-4 rounded-lg border ${colors[index] || 'bg-gray-50 text-gray-700 border-gray-200'}`}>
|
|
1164
|
-
<div className="flex items-center justify-between mb-2">
|
|
1165
|
-
<ShoppingCart className={`h-4 w-4 ${iconColors[index] || 'text-gray-500'}`} />
|
|
1166
|
-
<span className="text-xs font-medium">#{index + 1}</span>
|
|
1167
|
-
</div>
|
|
1168
|
-
<div className="text-lg font-bold">${category.total.toLocaleString()}</div>
|
|
1169
|
-
<div className="text-xs font-medium mb-1">{category.category}</div>
|
|
1170
|
-
<div className="text-xs opacity-75">{category.count} orders</div>
|
|
1171
|
-
</div>
|
|
1172
|
-
)
|
|
1173
|
-
})}
|
|
1174
|
-
</div>
|
|
1175
|
-
|
|
1176
|
-
{/* Enhanced Bar Chart for Category Performance */}
|
|
1177
|
-
<div className="bg-gradient-to-r from-slate-50 to-gray-50 p-6 rounded-lg border border-slate-200">
|
|
1178
|
-
<h3 className="text-lg font-semibold text-slate-800 mb-4 flex items-center gap-2">
|
|
1179
|
-
📊 Revenue by Category Performance
|
|
1180
|
-
</h3>
|
|
1181
|
-
<div className="h-80">
|
|
1182
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
1183
|
-
<RechartsBarChart
|
|
1184
|
-
data={orderData.map((item: any) => ({
|
|
1185
|
-
category: item.category,
|
|
1186
|
-
revenue: item.total,
|
|
1187
|
-
orders: item.count,
|
|
1188
|
-
avgOrderValue: Math.round(item.total / item.count),
|
|
1189
|
-
performance: (item.total / orderData.reduce((sum: number, cat: any) => sum + cat.total, 0)) * 100
|
|
1190
|
-
}))}
|
|
1191
|
-
margin={{ top: 20, right: 30, left: 20, bottom: 60 }}
|
|
1192
|
-
>
|
|
1193
|
-
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
|
1194
|
-
<XAxis
|
|
1195
|
-
dataKey="category"
|
|
1196
|
-
tick={{ fontSize: 12, fill: '#64748b' }}
|
|
1197
|
-
angle={-45}
|
|
1198
|
-
textAnchor="end"
|
|
1199
|
-
height={80}
|
|
1200
|
-
/>
|
|
1201
|
-
<YAxis tick={{ fontSize: 12, fill: '#64748b' }} />
|
|
1202
|
-
<Tooltip
|
|
1203
|
-
formatter={(value: any, name: string) => [
|
|
1204
|
-
name === 'revenue' ? `$${value.toLocaleString()}` : value,
|
|
1205
|
-
name === 'revenue' ? 'Revenue' : name === 'orders' ? 'Orders' : 'Avg Order Value'
|
|
1206
|
-
]}
|
|
1207
|
-
labelStyle={{ color: '#374151', fontWeight: 'bold' }}
|
|
1208
|
-
contentStyle={{
|
|
1209
|
-
backgroundColor: '#fff',
|
|
1210
|
-
border: '1px solid #e5e7eb',
|
|
1211
|
-
borderRadius: '8px',
|
|
1212
|
-
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
|
|
1213
|
-
}}
|
|
1214
|
-
/>
|
|
1215
|
-
<Legend />
|
|
1216
|
-
<Bar dataKey="revenue" name="Revenue" radius={[4, 4, 0, 0]}>
|
|
1217
|
-
{orderData.map((_entry: any, index: number) => {
|
|
1218
|
-
const colors = ['#10B981', '#3B82F6', '#8B5CF6', '#F59E0B', '#F43F5E', '#06B6D4', '#64748B', '#6366F1']
|
|
1219
|
-
return <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
|
|
1220
|
-
})}
|
|
1221
|
-
</Bar>
|
|
1222
|
-
<Bar dataKey="orders" name="Orders" radius={[4, 4, 0, 0]} fill="#94A3B8" fillOpacity={0.7} />
|
|
1223
|
-
</RechartsBarChart>
|
|
1224
|
-
</ResponsiveContainer>
|
|
1225
|
-
</div>
|
|
1226
|
-
</div>
|
|
1227
|
-
|
|
1228
|
-
{/* Category Performance Details */}
|
|
1229
|
-
<div className="space-y-3">
|
|
1230
|
-
<h3 className="text-sm font-semibold text-gray-700">Detailed Performance Breakdown</h3>
|
|
1231
|
-
{orderData.map((category: any, index: number) => {
|
|
1232
|
-
const percentage = Math.round((category.total / orderData.reduce((sum: number, cat: any) => sum + cat.total, 0)) * 100)
|
|
1233
|
-
const avgOrder = Math.round(category.total / category.count)
|
|
1234
|
-
const colors = ['bg-emerald-500', 'bg-blue-500', 'bg-violet-500', 'bg-amber-500', 'bg-rose-500', 'bg-cyan-500', 'bg-indigo-500']
|
|
1235
|
-
|
|
1236
|
-
return (
|
|
1237
|
-
<div key={index} className="flex items-center justify-between p-4 bg-white rounded-lg border border-gray-100 hover:shadow-sm transition-shadow">
|
|
1238
|
-
<div className="flex items-center space-x-4">
|
|
1239
|
-
<div className={`w-4 h-4 rounded-full ${colors[index] || 'bg-gray-400'}`} />
|
|
1240
|
-
<div>
|
|
1241
|
-
<div className="font-medium text-gray-900">{category.category}</div>
|
|
1242
|
-
<div className="text-sm text-gray-500">{category.count} orders • Avg: ${avgOrder}</div>
|
|
1243
|
-
</div>
|
|
1244
|
-
</div>
|
|
1245
|
-
<div className="text-right">
|
|
1246
|
-
<div className="font-bold text-gray-900">${category.total.toLocaleString()}</div>
|
|
1247
|
-
<div className="text-sm text-gray-500">{percentage}% of total</div>
|
|
1248
|
-
</div>
|
|
1249
|
-
</div>
|
|
1250
|
-
)
|
|
1251
|
-
})}
|
|
1252
|
-
</div>
|
|
1253
|
-
</div>
|
|
1254
|
-
) : (
|
|
1255
|
-
<div className="text-center text-gray-500 py-16">
|
|
1256
|
-
<ShoppingCart className="h-16 w-16 mx-auto text-gray-300 mb-4" />
|
|
1257
|
-
<p className="text-lg font-medium">No category data available</p>
|
|
1258
|
-
<p className="text-sm">Start selling products to see category performance</p>
|
|
1259
|
-
</div>
|
|
1260
|
-
)}
|
|
1261
|
-
</CardContent>
|
|
1262
|
-
</Card>
|
|
1263
|
-
|
|
1264
|
-
{/* Revenue Analytics Dashboard */}
|
|
1265
|
-
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
|
1266
|
-
<Card className="xl:col-span-2">
|
|
1267
|
-
<CardHeader>
|
|
1268
|
-
<div className="flex items-center justify-between">
|
|
1269
|
-
<div>
|
|
1270
|
-
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
|
1271
|
-
<DollarSign className="h-5 w-5 text-blue-600" />
|
|
1272
|
-
Revenue Analytics
|
|
1273
|
-
</CardTitle>
|
|
1274
|
-
<CardDescription className="text-sm text-gray-600 mt-1">
|
|
1275
|
-
Comprehensive revenue analysis with trends, forecasting, and performance metrics
|
|
1276
|
-
</CardDescription>
|
|
1277
|
-
</div>
|
|
1278
|
-
<div className="flex items-center space-x-2">
|
|
1279
|
-
<Badge variant="outline" className="text-blue-600 border-blue-200">
|
|
1280
|
-
${(totalRevenue / 1000).toFixed(1)}K Total
|
|
1281
|
-
</Badge>
|
|
1282
|
-
<Badge variant="outline" className="text-emerald-600 border-emerald-200">
|
|
1283
|
-
+{totalRevenue > 0 ? ((totalRevenue / 50000) * 100).toFixed(1) : 15.3}% Growth
|
|
1284
|
-
</Badge>
|
|
1285
|
-
</div>
|
|
1286
|
-
</div>
|
|
1287
|
-
</CardHeader>
|
|
1288
|
-
<CardContent>
|
|
1289
|
-
<div className="space-y-6">
|
|
1290
|
-
{/* Revenue Metrics Grid */}
|
|
1291
|
-
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
1292
|
-
<div className="p-4 bg-gradient-to-br from-emerald-50 to-emerald-100 rounded-lg border border-emerald-200">
|
|
1293
|
-
<div className="flex items-center justify-between mb-2">
|
|
1294
|
-
<DollarSign className="h-5 w-5 text-emerald-600" />
|
|
1295
|
-
<ArrowUpRight className="h-4 w-4 text-emerald-600" />
|
|
1296
|
-
</div>
|
|
1297
|
-
<div className="text-2xl font-bold text-emerald-800">${(totalRevenue / 1000).toFixed(1)}K</div>
|
|
1298
|
-
<div className="text-sm text-emerald-600 font-medium">Total Revenue</div>
|
|
1299
|
-
<div className="text-xs text-emerald-600">+12.5% vs last month</div>
|
|
1300
|
-
</div>
|
|
1301
|
-
|
|
1302
|
-
<div className="p-4 bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg border border-blue-200">
|
|
1303
|
-
<div className="flex items-center justify-between mb-2">
|
|
1304
|
-
<TrendingUp className="h-5 w-5 text-blue-600" />
|
|
1305
|
-
<ArrowUpRight className="h-4 w-4 text-blue-600" />
|
|
1306
|
-
</div>
|
|
1307
|
-
<div className="text-2xl font-bold text-blue-800">${averageOrderValue.toFixed(0)}</div>
|
|
1308
|
-
<div className="text-sm text-blue-600 font-medium">Avg Order Value</div>
|
|
1309
|
-
<div className="text-xs text-blue-600">+8.2% vs last month</div>
|
|
1310
|
-
</div>
|
|
1311
|
-
|
|
1312
|
-
<div className="p-4 bg-gradient-to-br from-violet-50 to-violet-100 rounded-lg border border-violet-200">
|
|
1313
|
-
<div className="flex items-center justify-between mb-2">
|
|
1314
|
-
<Users className="h-5 w-5 text-violet-600" />
|
|
1315
|
-
<ArrowUpRight className="h-4 w-4 text-violet-600" />
|
|
1316
|
-
</div>
|
|
1317
|
-
<div className="text-2xl font-bold text-violet-800">{monthlyRevenueData.length}</div>
|
|
1318
|
-
<div className="text-sm text-violet-600 font-medium">Active Months</div>
|
|
1319
|
-
<div className="text-xs text-violet-600">Growing steadily</div>
|
|
1320
|
-
</div>
|
|
1321
|
-
|
|
1322
|
-
<div className="p-4 bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg border border-amber-200">
|
|
1323
|
-
<div className="flex items-center justify-between mb-2">
|
|
1324
|
-
<ShoppingCart className="h-5 w-5 text-amber-600" />
|
|
1325
|
-
<ArrowUpRight className="h-4 w-4 text-amber-600" />
|
|
1326
|
-
</div>
|
|
1327
|
-
<div className="text-2xl font-bold text-amber-800">{totalOrders}</div>
|
|
1328
|
-
<div className="text-sm text-amber-600 font-medium">Total Orders</div>
|
|
1329
|
-
<div className="text-xs text-amber-600">+15.3% growth</div>
|
|
1330
|
-
</div>
|
|
1331
|
-
</div>
|
|
1332
|
-
|
|
1333
|
-
{/* Revenue Trend Analysis */}
|
|
1334
|
-
{monthlyRevenueData.length > 0 && (
|
|
1335
|
-
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 p-6 rounded-lg border border-blue-200">
|
|
1336
|
-
<h3 className="text-lg font-semibold text-blue-800 mb-4 flex items-center gap-2">
|
|
1337
|
-
<TrendingUp className="h-5 w-5 text-blue-600" />
|
|
1338
|
-
Revenue Trend Analysis
|
|
1339
|
-
</h3>
|
|
1340
|
-
<AreaChart
|
|
1341
|
-
className="h-80"
|
|
1342
|
-
data={monthlyRevenueData.map((item: any) => ({
|
|
1343
|
-
...item,
|
|
1344
|
-
growth: monthlyRevenueData.indexOf(item) > 0 ?
|
|
1345
|
-
((item.revenue - monthlyRevenueData[monthlyRevenueData.indexOf(item) - 1].revenue) /
|
|
1346
|
-
monthlyRevenueData[monthlyRevenueData.indexOf(item) - 1].revenue * 100) : 0,
|
|
1347
|
-
cumulative: monthlyRevenueData.slice(0, monthlyRevenueData.indexOf(item) + 1)
|
|
1348
|
-
.reduce((sum: number, month: any) => sum + month.revenue, 0)
|
|
1349
|
-
}))}
|
|
1350
|
-
index="month"
|
|
1351
|
-
categories={["revenue", "cumulative"]}
|
|
1352
|
-
colors={["blue", "emerald"]}
|
|
1353
|
-
valueFormatter={(number: number) =>
|
|
1354
|
-
`$${Intl.NumberFormat("us").format(number)}`
|
|
1355
|
-
}
|
|
1356
|
-
showLegend={true}
|
|
1357
|
-
showGridLines={true}
|
|
1358
|
-
showXAxis={true}
|
|
1359
|
-
showYAxis={true}
|
|
1360
|
-
/>
|
|
1361
|
-
</div>
|
|
1362
|
-
)}
|
|
1363
|
-
|
|
1364
|
-
{/* Revenue Distribution by Category */}
|
|
1365
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
1366
|
-
<div className="bg-white p-6 rounded-lg border border-gray-200">
|
|
1367
|
-
<h3 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
|
1368
|
-
<PieChart className="h-5 w-5 text-gray-600" />
|
|
1369
|
-
Revenue Distribution
|
|
1370
|
-
</h3>
|
|
1371
|
-
<DonutChart
|
|
1372
|
-
className="h-64"
|
|
1373
|
-
data={orderData.map((item: any) => ({
|
|
1374
|
-
name: item.category,
|
|
1375
|
-
value: item.total,
|
|
1376
|
-
percentage: Math.round((item.total / orderData.reduce((sum: number, cat: any) => sum + cat.total, 0)) * 100)
|
|
1377
|
-
}))}
|
|
1378
|
-
category="value"
|
|
1379
|
-
index="name"
|
|
1380
|
-
valueFormatter={(number: number) =>
|
|
1381
|
-
`$${Intl.NumberFormat("us").format(number)}`
|
|
1382
|
-
}
|
|
1383
|
-
colors={["emerald", "blue", "violet", "amber", "rose", "cyan"]}
|
|
1384
|
-
/>
|
|
1385
|
-
</div>
|
|
1386
|
-
|
|
1387
|
-
<div className="bg-white p-6 rounded-lg border border-gray-200">
|
|
1388
|
-
<h3 className="text-lg font-semibold text-gray-800 mb-4">Revenue Insights</h3>
|
|
1389
|
-
<div className="space-y-4">
|
|
1390
|
-
{/* Top Revenue Category */}
|
|
1391
|
-
<div className="p-4 bg-gradient-to-r from-emerald-50 to-emerald-100 rounded-lg border border-emerald-200">
|
|
1392
|
-
<div className="flex items-center justify-between">
|
|
1393
|
-
<div>
|
|
1394
|
-
<div className="text-sm text-emerald-600 font-medium">Top Revenue Category</div>
|
|
1395
|
-
<div className="text-xl font-bold text-emerald-800">
|
|
1396
|
-
{orderData.length > 0 ? orderData.sort((a: any, b: any) => b.total - a.total)[0]?.category || 'N/A' : 'Electronics'}
|
|
1397
|
-
</div>
|
|
1398
|
-
</div>
|
|
1399
|
-
<div className="text-right">
|
|
1400
|
-
<div className="text-lg font-bold text-emerald-700">
|
|
1401
|
-
${orderData.length > 0 ? orderData.sort((a: any, b: any) => b.total - a.total)[0]?.total?.toLocaleString() || '0' : '8,750'}
|
|
1402
|
-
</div>
|
|
1403
|
-
<div className="text-xs text-emerald-600">
|
|
1404
|
-
{orderData.length > 0 ? Math.round((orderData.sort((a: any, b: any) => b.total - a.total)[0]?.total || 0) / orderData.reduce((sum: number, cat: any) => sum + cat.total, 0) * 100) : 35}% of total
|
|
1405
|
-
</div>
|
|
1406
|
-
</div>
|
|
1407
|
-
</div>
|
|
1408
|
-
</div>
|
|
1409
|
-
|
|
1410
|
-
{/* Revenue Growth Prediction */}
|
|
1411
|
-
<div className="p-4 bg-gradient-to-r from-blue-50 to-blue-100 rounded-lg border border-blue-200">
|
|
1412
|
-
<div className="text-sm text-blue-600 font-medium">Projected Growth</div>
|
|
1413
|
-
<div className="text-xl font-bold text-blue-800">
|
|
1414
|
-
+{totalRevenue > 0 ? ((totalRevenue / 50000) * 100).toFixed(1) : 18.5}%
|
|
1415
|
-
</div>
|
|
1416
|
-
<div className="text-xs text-blue-600">Next quarter forecast</div>
|
|
1417
|
-
<div className="mt-2">
|
|
1418
|
-
<SparkAreaChart
|
|
1419
|
-
data={monthlyRevenueData.slice(-6)}
|
|
1420
|
-
categories={["revenue"]}
|
|
1421
|
-
index="month"
|
|
1422
|
-
colors={["blue"]}
|
|
1423
|
-
className="h-8 w-full"
|
|
1424
|
-
/>
|
|
1425
|
-
</div>
|
|
1426
|
-
</div>
|
|
1427
|
-
|
|
1428
|
-
{/* Performance Score */}
|
|
1429
|
-
<div className="p-4 bg-gradient-to-r from-violet-50 to-violet-100 rounded-lg border border-violet-200">
|
|
1430
|
-
<div className="text-sm text-violet-600 font-medium">Performance Score</div>
|
|
1431
|
-
<div className="flex items-center space-x-2">
|
|
1432
|
-
<div className="text-xl font-bold text-violet-800">
|
|
1433
|
-
{Math.min(100, Math.round((totalRevenue / 30000) * 100))}%
|
|
1434
|
-
</div>
|
|
1435
|
-
<div className="flex-1 bg-violet-200 rounded-full h-2">
|
|
1436
|
-
<div
|
|
1437
|
-
className="bg-violet-500 h-2 rounded-full transition-all duration-1000"
|
|
1438
|
-
style={{ width: `${Math.min(100, Math.round((totalRevenue / 30000) * 100))}%` }}
|
|
1439
|
-
/>
|
|
1440
|
-
</div>
|
|
1441
|
-
</div>
|
|
1442
|
-
<div className="text-xs text-violet-600 mt-1">Excellent performance</div>
|
|
1443
|
-
</div>
|
|
1444
|
-
</div>
|
|
1445
|
-
</div>
|
|
1446
|
-
</div>
|
|
1447
|
-
</div>
|
|
1448
|
-
</CardContent>
|
|
1449
|
-
</Card>
|
|
1450
|
-
</div>
|
|
1451
|
-
|
|
1452
|
-
{/* Advanced Data Table Section */}
|
|
1453
|
-
<Card>
|
|
1454
|
-
<CardHeader>
|
|
1455
|
-
<CardTitle>Recent Transactions</CardTitle>
|
|
1456
|
-
<CardDescription>
|
|
1457
|
-
Latest transaction data with comprehensive details
|
|
1458
|
-
</CardDescription>
|
|
1459
|
-
</CardHeader>
|
|
1460
|
-
<CardContent>
|
|
1461
|
-
<Table>
|
|
1462
|
-
<TableHead>
|
|
1463
|
-
<TableRow>
|
|
1464
|
-
<TableHeaderCell>Transaction ID</TableHeaderCell>
|
|
1465
|
-
<TableHeaderCell>Customer</TableHeaderCell>
|
|
1466
|
-
<TableHeaderCell>Category</TableHeaderCell>
|
|
1467
|
-
<TableHeaderCell>Amount</TableHeaderCell>
|
|
1468
|
-
<TableHeaderCell>Payment Method</TableHeaderCell>
|
|
1469
|
-
<TableHeaderCell>Date</TableHeaderCell>
|
|
1470
|
-
</TableRow>
|
|
1471
|
-
</TableHead>
|
|
1472
|
-
<TableBody>
|
|
1473
|
-
{recentTransactions.map((transaction: any) => (
|
|
1474
|
-
<TableRow key={transaction.id}>
|
|
1475
|
-
<TableCell className="font-medium">{transaction.id}</TableCell>
|
|
1476
|
-
<TableCell>{transaction.customer}</TableCell>
|
|
1477
|
-
<TableCell>
|
|
1478
|
-
<Badge variant="outline" className="text-xs">
|
|
1479
|
-
{transaction.category}
|
|
1480
|
-
</Badge>
|
|
1481
|
-
</TableCell>
|
|
1482
|
-
<TableCell className="font-semibold text-emerald-600">
|
|
1483
|
-
${transaction.amount.toLocaleString()}
|
|
1484
|
-
</TableCell>
|
|
1485
|
-
<TableCell>{transaction.payment_method}</TableCell>
|
|
1486
|
-
<TableCell className="text-gray-500">{transaction.date}</TableCell>
|
|
1487
|
-
</TableRow>
|
|
1488
|
-
))}
|
|
1489
|
-
</TableBody>
|
|
1490
|
-
</Table>
|
|
1491
|
-
</CardContent>
|
|
1492
|
-
</Card>
|
|
1493
|
-
|
|
1494
|
-
{/* Data Source Info */}
|
|
1495
|
-
{userQuery.data?.user && (
|
|
1496
|
-
<Alert>
|
|
1497
|
-
<AlertCircle className="h-4 w-4" />
|
|
1498
|
-
<AlertDescription>
|
|
1499
|
-
Data is being fetched from Copa-lib APIs. Current user: {userQuery.data.user.name}
|
|
1500
|
-
({userQuery.data.user.organization || 'No organization'})
|
|
1501
|
-
</AlertDescription>
|
|
1502
|
-
</Alert>
|
|
1503
|
-
)}
|
|
1504
|
-
</div>
|
|
1505
|
-
)
|
|
1506
|
-
}
|