create-bluecopa-react-app 1.0.5 → 1.0.6
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 +47 -10
- package/bin/create-bluecopa-react-app.js +257 -51
- package/package.json +6 -5
- package/templates/latest/Agent.md +254 -0
- package/templates/latest/Dockerfile +22 -0
- package/templates/latest/README.md +157 -221
- package/templates/latest/app/app.css +134 -0
- package/templates/latest/app/app.tsx +46 -0
- package/templates/latest/app/components/app-sidebar.tsx +174 -0
- package/templates/latest/app/components/chart-area-interactive.tsx +290 -0
- package/templates/latest/app/components/data-table.tsx +807 -0
- package/templates/latest/app/components/nav-documents.tsx +92 -0
- package/templates/latest/app/components/nav-main.tsx +56 -0
- package/templates/latest/app/components/nav-secondary.tsx +42 -0
- package/templates/latest/app/components/nav-user.tsx +112 -0
- package/templates/latest/app/components/section-cards.tsx +102 -0
- package/templates/latest/app/components/site-header.tsx +19 -0
- package/templates/latest/app/components/ui/avatar.tsx +53 -0
- package/templates/latest/app/components/ui/badge.tsx +46 -0
- package/templates/latest/app/components/ui/breadcrumb.tsx +109 -0
- package/templates/latest/app/components/ui/button.tsx +58 -0
- package/templates/latest/app/components/ui/card.tsx +92 -0
- package/templates/latest/app/components/ui/chart.tsx +352 -0
- package/templates/latest/app/components/ui/checkbox.tsx +30 -0
- package/templates/latest/app/components/ui/drawer.tsx +139 -0
- package/templates/latest/app/components/ui/dropdown-menu.tsx +258 -0
- package/templates/latest/app/components/ui/input.tsx +21 -0
- package/templates/latest/app/components/ui/label.tsx +24 -0
- package/templates/latest/app/components/ui/select.tsx +183 -0
- package/templates/latest/app/components/ui/separator.tsx +26 -0
- package/templates/latest/app/components/ui/sheet.tsx +139 -0
- package/templates/latest/app/components/ui/sidebar.tsx +731 -0
- package/templates/latest/app/components/ui/skeleton.tsx +13 -0
- package/templates/latest/app/components/ui/sonner.tsx +23 -0
- package/templates/latest/app/components/ui/table.tsx +117 -0
- package/templates/latest/app/components/ui/tabs.tsx +66 -0
- package/templates/latest/app/components/ui/toggle-group.tsx +73 -0
- package/templates/latest/app/components/ui/toggle.tsx +47 -0
- package/templates/latest/app/components/ui/tooltip.tsx +59 -0
- package/templates/latest/app/dashboard/data.json +614 -0
- package/templates/latest/app/hooks/use-bluecopa-user.ts +37 -0
- package/templates/latest/app/hooks/use-mobile.ts +19 -0
- package/templates/latest/{src → app}/lib/utils.ts +1 -1
- package/templates/latest/app/main.tsx +12 -0
- package/templates/latest/app/routes/home.tsx +40 -0
- package/templates/latest/app/routes.tsx +15 -0
- package/templates/latest/{src → app}/single-spa.tsx +3 -3
- package/templates/latest/components.json +22 -0
- package/templates/latest/dist/assets/__federation_expose_App-DRwKKpS2.js +91 -0
- package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +438 -0
- package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +16 -0
- package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +17 -0
- package/templates/latest/dist/assets/client-DgSav55y.js +12658 -0
- package/templates/latest/dist/assets/home-DOL6GrYV.js +54951 -0
- package/templates/latest/dist/assets/index-BzNimew1.js +69 -0
- package/templates/latest/dist/assets/index-DMFtQdNS.js +412 -0
- package/templates/latest/dist/assets/index-DdYpcDMk.js +60 -0
- package/templates/latest/dist/assets/remoteEntry.js +88 -0
- package/templates/latest/dist/assets/style-36A39bNN.css +3683 -0
- package/templates/latest/dist/avatars/shadcn.svg +6 -0
- package/templates/latest/dist/favicon.ico +0 -0
- package/templates/latest/dist/index.html +19 -0
- package/templates/latest/index.html +1 -1
- package/templates/latest/package-lock.json +1227 -3353
- package/templates/latest/package.json +47 -43
- package/templates/latest/pnpm-lock.yaml +4767 -0
- package/templates/latest/preview/index.html +32 -2
- package/templates/latest/public/avatars/shadcn.svg +6 -0
- package/templates/latest/public/favicon.ico +0 -0
- package/templates/latest/tsconfig.json +19 -12
- package/templates/latest/vite.config.ts +41 -41
- package/templates/latest/.env.example +0 -14
- package/templates/latest/.eslintrc.cjs +0 -42
- package/templates/latest/AGENT.md +0 -282
- package/templates/latest/clean.sh +0 -40
- package/templates/latest/postcss.config.cjs +0 -6
- package/templates/latest/public/bluecopa-logo.svg +0 -30
- package/templates/latest/public/favicon-32x32.png +0 -0
- package/templates/latest/public/favicon-96x96.png +0 -0
- package/templates/latest/setup.sh +0 -56
- package/templates/latest/src/App.tsx +0 -19
- package/templates/latest/src/components/charts/AreaChart.tsx +0 -80
- package/templates/latest/src/components/charts/DonutChart.tsx +0 -73
- package/templates/latest/src/components/charts/SparkAreaChart.tsx +0 -52
- package/templates/latest/src/components/layout/dashboard-header.tsx +0 -139
- package/templates/latest/src/components/layout/dashboard-layout.tsx +0 -37
- package/templates/latest/src/components/layout/navbar.tsx +0 -106
- package/templates/latest/src/components/layout/sidebar.tsx +0 -55
- package/templates/latest/src/components/page/dashboard/DashboardMetrics.tsx +0 -97
- package/templates/latest/src/components/page/dashboard/PaymentMethodsAnalysis.tsx +0 -182
- package/templates/latest/src/components/page/dashboard/RevenueAnalytics.tsx +0 -505
- package/templates/latest/src/components/page/dashboard/SalesAnalytics.tsx +0 -313
- package/templates/latest/src/components/page/dashboard/TransactionsTable.tsx +0 -256
- package/templates/latest/src/components/page/dashboard/dashboard-utils.ts +0 -147
- package/templates/latest/src/components/page/dashboard/dashboard.tsx +0 -185
- package/templates/latest/src/components/tables/data-grid.tsx +0 -439
- package/templates/latest/src/components/ui/alert.tsx +0 -59
- package/templates/latest/src/components/ui/avatar.tsx +0 -50
- package/templates/latest/src/components/ui/badge.tsx +0 -36
- package/templates/latest/src/components/ui/bluecopa-logo.tsx +0 -57
- package/templates/latest/src/components/ui/button.tsx +0 -58
- package/templates/latest/src/components/ui/card.tsx +0 -79
- package/templates/latest/src/components/ui/dropdown-menu.tsx +0 -200
- package/templates/latest/src/components/ui/input.tsx +0 -24
- package/templates/latest/src/components/ui/label.tsx +0 -21
- package/templates/latest/src/components/ui/select.tsx +0 -27
- package/templates/latest/src/hooks/use-api.ts +0 -55
- package/templates/latest/src/index.css +0 -59
- package/templates/latest/src/main.tsx +0 -13
- package/templates/latest/src/pages/Dashboard.tsx +0 -13
- package/templates/latest/src/pages/Home.tsx +0 -622
- package/templates/latest/src/providers/query-provider.tsx +0 -48
- package/templates/latest/src/types/api.ts +0 -78
- package/templates/latest/src/vite-env.d.ts +0 -11
- package/templates/latest/tailwind.config.js +0 -87
- package/templates/latest/tsconfig.app.json +0 -32
- package/templates/latest/tsconfig.node.json +0 -14
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Routes, Route, Navigate } from 'react-router-dom'
|
|
2
|
-
import { Suspense, lazy } from 'react'
|
|
3
|
-
import QueryProvider from '@/providers/query-provider'
|
|
4
|
-
import Home from '@/pages/Home'
|
|
5
|
-
const Dashboard = lazy(() => import('@/pages/Dashboard'))
|
|
6
|
-
|
|
7
|
-
export default function App() {
|
|
8
|
-
return (
|
|
9
|
-
<QueryProvider>
|
|
10
|
-
<Suspense fallback={<div className="p-4 text-sm text-muted-foreground">Loading…</div>}>
|
|
11
|
-
<Routes>
|
|
12
|
-
<Route path="/" element={<Home />} />
|
|
13
|
-
<Route path="/dashboard" element={<Dashboard />} />
|
|
14
|
-
<Route path="*" element={<Navigate to="/" replace />} />
|
|
15
|
-
</Routes>
|
|
16
|
-
</Suspense>
|
|
17
|
-
</QueryProvider>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ResponsiveContainer,
|
|
4
|
-
AreaChart as RechartsAreaChart,
|
|
5
|
-
Area,
|
|
6
|
-
XAxis,
|
|
7
|
-
YAxis,
|
|
8
|
-
CartesianGrid,
|
|
9
|
-
Tooltip,
|
|
10
|
-
Legend
|
|
11
|
-
} from 'recharts';
|
|
12
|
-
|
|
13
|
-
interface AreaChartProps {
|
|
14
|
-
data: any[];
|
|
15
|
-
index: string;
|
|
16
|
-
categories: string[];
|
|
17
|
-
colors?: string[];
|
|
18
|
-
className?: string;
|
|
19
|
-
valueFormatter?: (value: any) => any;
|
|
20
|
-
showLegend?: boolean;
|
|
21
|
-
showGridLines?: boolean;
|
|
22
|
-
showXAxis?: boolean;
|
|
23
|
-
showYAxis?: boolean;
|
|
24
|
-
yAxisWidth?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const AreaChart: React.FC<AreaChartProps> = ({
|
|
28
|
-
data,
|
|
29
|
-
index,
|
|
30
|
-
categories,
|
|
31
|
-
colors = ['#3B82F6'],
|
|
32
|
-
className = "h-80",
|
|
33
|
-
valueFormatter,
|
|
34
|
-
showLegend = true,
|
|
35
|
-
showGridLines = true,
|
|
36
|
-
showXAxis = true,
|
|
37
|
-
showYAxis = true,
|
|
38
|
-
yAxisWidth
|
|
39
|
-
}) => {
|
|
40
|
-
const colorMap: { [key: string]: string } = {
|
|
41
|
-
blue: '#3B82F6',
|
|
42
|
-
emerald: '#10B981',
|
|
43
|
-
violet: '#8B5CF6',
|
|
44
|
-
rose: '#F43F5E',
|
|
45
|
-
amber: '#F59E0B',
|
|
46
|
-
cyan: '#06B6D4',
|
|
47
|
-
slate: '#64748B',
|
|
48
|
-
indigo: '#6366F1'
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const getColor = (color: string, index: number) => {
|
|
52
|
-
return colorMap[color] || colors[index] || '#3B82F6';
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div className={className}>
|
|
57
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
58
|
-
<RechartsAreaChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
|
59
|
-
{showGridLines && <CartesianGrid strokeDasharray="3 3" />}
|
|
60
|
-
{showXAxis && <XAxis dataKey={index} />}
|
|
61
|
-
{showYAxis && <YAxis width={yAxisWidth || 60} />}
|
|
62
|
-
<Tooltip
|
|
63
|
-
formatter={(value: any) => valueFormatter ? valueFormatter(value) : value}
|
|
64
|
-
/>
|
|
65
|
-
{showLegend && <Legend />}
|
|
66
|
-
{categories?.map((category: string, idx: number) => (
|
|
67
|
-
<Area
|
|
68
|
-
key={category}
|
|
69
|
-
type="monotone"
|
|
70
|
-
dataKey={category}
|
|
71
|
-
stroke={getColor(colors[idx], idx)}
|
|
72
|
-
fill={getColor(colors[idx], idx)}
|
|
73
|
-
fillOpacity={0.3}
|
|
74
|
-
/>
|
|
75
|
-
))}
|
|
76
|
-
</RechartsAreaChart>
|
|
77
|
-
</ResponsiveContainer>
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ResponsiveContainer,
|
|
4
|
-
PieChart,
|
|
5
|
-
Pie,
|
|
6
|
-
Cell,
|
|
7
|
-
Tooltip,
|
|
8
|
-
Legend
|
|
9
|
-
} from 'recharts';
|
|
10
|
-
|
|
11
|
-
interface DonutChartProps {
|
|
12
|
-
data: any[];
|
|
13
|
-
category: string;
|
|
14
|
-
index: string;
|
|
15
|
-
colors?: string[];
|
|
16
|
-
className?: string;
|
|
17
|
-
valueFormatter?: (value: any) => any;
|
|
18
|
-
variant?: "donut" | "pie";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const DonutChart: React.FC<DonutChartProps> = ({
|
|
22
|
-
data,
|
|
23
|
-
category,
|
|
24
|
-
index,
|
|
25
|
-
colors = ['#3B82F6'],
|
|
26
|
-
className = "h-64",
|
|
27
|
-
valueFormatter,
|
|
28
|
-
variant = "donut"
|
|
29
|
-
}) => {
|
|
30
|
-
const colorMap: { [key: string]: string } = {
|
|
31
|
-
blue: '#3B82F6',
|
|
32
|
-
emerald: '#10B981',
|
|
33
|
-
violet: '#8B5CF6',
|
|
34
|
-
rose: '#F43F5E',
|
|
35
|
-
amber: '#F59E0B',
|
|
36
|
-
cyan: '#06B6D4',
|
|
37
|
-
slate: '#64748B',
|
|
38
|
-
indigo: '#6366F1'
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const getColor = (color: string, index: number) => {
|
|
42
|
-
return colorMap[color] || colors[index] || '#3B82F6';
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const COLORS = colors.map((color: string, idx: number) => getColor(color, idx));
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div className={className}>
|
|
49
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
50
|
-
<PieChart>
|
|
51
|
-
<Pie
|
|
52
|
-
data={data}
|
|
53
|
-
cx="50%"
|
|
54
|
-
cy="50%"
|
|
55
|
-
innerRadius={variant === "donut" ? 60 : 0}
|
|
56
|
-
outerRadius={80}
|
|
57
|
-
paddingAngle={5}
|
|
58
|
-
dataKey={category}
|
|
59
|
-
nameKey={index}
|
|
60
|
-
>
|
|
61
|
-
{data.map((_entry: any, index: number) => (
|
|
62
|
-
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
|
63
|
-
))}
|
|
64
|
-
</Pie>
|
|
65
|
-
<Tooltip
|
|
66
|
-
formatter={(value: any) => valueFormatter ? valueFormatter(value) : value}
|
|
67
|
-
/>
|
|
68
|
-
<Legend />
|
|
69
|
-
</PieChart>
|
|
70
|
-
</ResponsiveContainer>
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ResponsiveContainer,
|
|
4
|
-
AreaChart as RechartsAreaChart,
|
|
5
|
-
Area
|
|
6
|
-
} from 'recharts';
|
|
7
|
-
|
|
8
|
-
interface SparkAreaChartProps {
|
|
9
|
-
data: any[];
|
|
10
|
-
categories: string[];
|
|
11
|
-
colors?: string[];
|
|
12
|
-
className?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const SparkAreaChart: React.FC<SparkAreaChartProps> = ({
|
|
16
|
-
data,
|
|
17
|
-
categories,
|
|
18
|
-
colors = ['#3B82F6'],
|
|
19
|
-
className = "h-10"
|
|
20
|
-
}) => {
|
|
21
|
-
const colorMap: { [key: string]: string } = {
|
|
22
|
-
blue: '#3B82F6',
|
|
23
|
-
emerald: '#10B981',
|
|
24
|
-
violet: '#8B5CF6',
|
|
25
|
-
rose: '#F43F5E',
|
|
26
|
-
amber: '#F59E0B',
|
|
27
|
-
cyan: '#06B6D4'
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const getColor = (color: string, index: number) => {
|
|
31
|
-
return colorMap[color] || colors[index] || '#3B82F6';
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className={className}>
|
|
36
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
37
|
-
<RechartsAreaChart data={data} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
|
38
|
-
{categories?.map((category: string, idx: number) => (
|
|
39
|
-
<Area
|
|
40
|
-
key={category}
|
|
41
|
-
type="monotone"
|
|
42
|
-
dataKey={category}
|
|
43
|
-
stroke={getColor(colors[idx], idx)}
|
|
44
|
-
fill={getColor(colors[idx], idx)}
|
|
45
|
-
fillOpacity={0.6}
|
|
46
|
-
/>
|
|
47
|
-
))}
|
|
48
|
-
</RechartsAreaChart>
|
|
49
|
-
</ResponsiveContainer>
|
|
50
|
-
</div>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { Bell, Search, User, Settings, LogOut, ChevronDown } from 'lucide-react'
|
|
2
|
-
import { Button } from '@/components/ui/button'
|
|
3
|
-
import {
|
|
4
|
-
DropdownMenu,
|
|
5
|
-
DropdownMenuContent,
|
|
6
|
-
DropdownMenuItem,
|
|
7
|
-
DropdownMenuLabel,
|
|
8
|
-
DropdownMenuSeparator,
|
|
9
|
-
DropdownMenuTrigger,
|
|
10
|
-
} from '@/components/ui/dropdown-menu'
|
|
11
|
-
import { Input } from '@/components/ui/input'
|
|
12
|
-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
13
|
-
import { useUserData } from '@/hooks/use-api'
|
|
14
|
-
|
|
15
|
-
interface DashboardHeaderProps {
|
|
16
|
-
title?: string
|
|
17
|
-
subtitle?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default function DashboardHeader({ title, subtitle }: DashboardHeaderProps) {
|
|
21
|
-
const { data: userData, isLoading } = useUserData()
|
|
22
|
-
|
|
23
|
-
// Extract user from the API response structure
|
|
24
|
-
const user = userData?.user
|
|
25
|
-
const organization = userData?.organization
|
|
26
|
-
|
|
27
|
-
// Create display values with proper fallbacks
|
|
28
|
-
const getDisplayName = () => {
|
|
29
|
-
if (!user) return 'Loading...'
|
|
30
|
-
|
|
31
|
-
// If firstName and lastName are the same as email, just use email
|
|
32
|
-
if (user.firstName === user.email && user.lastName === user.email) {
|
|
33
|
-
return user.email
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Try to construct name from firstName and lastName
|
|
37
|
-
const fullName = `${user.firstName || ''} ${user.lastName || ''}`.trim()
|
|
38
|
-
return fullName || user.email
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const displayName = getDisplayName()
|
|
42
|
-
const displayRole = organization?.name || 'User' // Use organization name as role
|
|
43
|
-
const displayEmail = user?.email || 'loading@example.com'
|
|
44
|
-
|
|
45
|
-
// Show loading state in user info
|
|
46
|
-
if (isLoading) {
|
|
47
|
-
return (
|
|
48
|
-
<header className="bg-white border-b border-gray-200 px-6 py-4">
|
|
49
|
-
<div className="flex items-center justify-between">
|
|
50
|
-
<div className="flex-1">
|
|
51
|
-
{title && (
|
|
52
|
-
<div>
|
|
53
|
-
<h1 className="text-2xl font-semibold text-gray-900">{title}</h1>
|
|
54
|
-
{subtitle && <p className="text-sm text-gray-500 mt-1">{subtitle}</p>}
|
|
55
|
-
</div>
|
|
56
|
-
)}
|
|
57
|
-
</div>
|
|
58
|
-
<div className="flex items-center space-x-4">
|
|
59
|
-
<div className="text-sm text-gray-500">Loading user...</div>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
</header>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<header className="bg-white border-b border-gray-200 px-6 py-4">
|
|
68
|
-
<div className="flex items-center justify-between">
|
|
69
|
-
<div className="flex-1">
|
|
70
|
-
{title && (
|
|
71
|
-
<div>
|
|
72
|
-
<h1 className="text-2xl font-semibold text-gray-900">{title}</h1>
|
|
73
|
-
{subtitle && <p className="text-sm text-gray-500 mt-1">{subtitle}</p>}
|
|
74
|
-
</div>
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<div className="flex items-center space-x-4">
|
|
79
|
-
{/* Search */}
|
|
80
|
-
<div className="relative hidden md:block">
|
|
81
|
-
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
82
|
-
<Input
|
|
83
|
-
type="search"
|
|
84
|
-
placeholder="Search..."
|
|
85
|
-
className="w-80 pl-10 pr-4"
|
|
86
|
-
/>
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
|
-
{/* Notifications */}
|
|
90
|
-
<Button variant="ghost" size="sm" className="relative" aria-label="Open notifications, 3 unread">
|
|
91
|
-
<Bell className="h-5 w-5" />
|
|
92
|
-
<span className="absolute -top-1 -right-1 h-4 w-4 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
|
|
93
|
-
3
|
|
94
|
-
</span>
|
|
95
|
-
</Button>
|
|
96
|
-
|
|
97
|
-
{/* User Menu */}
|
|
98
|
-
<DropdownMenu>
|
|
99
|
-
<DropdownMenuTrigger asChild>
|
|
100
|
-
<Button variant="ghost" className="flex items-center space-x-2 p-2">
|
|
101
|
-
<Avatar className="h-8 w-8">
|
|
102
|
-
<AvatarImage src={undefined} alt={displayName} />
|
|
103
|
-
<AvatarFallback>{displayName.split(' ').map((n: string) => n[0]).join('')}</AvatarFallback>
|
|
104
|
-
</Avatar>
|
|
105
|
-
<div className="hidden md:block text-left">
|
|
106
|
-
<p className="text-sm font-medium text-gray-900">{displayName}</p>
|
|
107
|
-
<p className="text-xs text-gray-500">{displayRole}</p>
|
|
108
|
-
</div>
|
|
109
|
-
<ChevronDown className="h-4 w-4 text-gray-400" />
|
|
110
|
-
</Button>
|
|
111
|
-
</DropdownMenuTrigger>
|
|
112
|
-
<DropdownMenuContent align="end" className="w-56">
|
|
113
|
-
<DropdownMenuLabel>
|
|
114
|
-
<div>
|
|
115
|
-
<p className="font-medium">{displayName}</p>
|
|
116
|
-
<p className="text-xs text-gray-500">{displayEmail}</p>
|
|
117
|
-
</div>
|
|
118
|
-
</DropdownMenuLabel>
|
|
119
|
-
<DropdownMenuSeparator />
|
|
120
|
-
<DropdownMenuItem>
|
|
121
|
-
<User className="mr-2 h-4 w-4" />
|
|
122
|
-
Profile
|
|
123
|
-
</DropdownMenuItem>
|
|
124
|
-
<DropdownMenuItem>
|
|
125
|
-
<Settings className="mr-2 h-4 w-4" />
|
|
126
|
-
Settings
|
|
127
|
-
</DropdownMenuItem>
|
|
128
|
-
<DropdownMenuSeparator />
|
|
129
|
-
<DropdownMenuItem className="text-red-600">
|
|
130
|
-
<LogOut className="mr-2 h-4 w-4" />
|
|
131
|
-
Sign out
|
|
132
|
-
</DropdownMenuItem>
|
|
133
|
-
</DropdownMenuContent>
|
|
134
|
-
</DropdownMenu>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
</header>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
2
|
-
import Sidebar from "./sidebar";
|
|
3
|
-
import DashboardHeader from "./dashboard-header";
|
|
4
|
-
|
|
5
|
-
interface DashboardLayoutProps {
|
|
6
|
-
children: ReactNode;
|
|
7
|
-
title?: string;
|
|
8
|
-
subtitle?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function DashboardLayout({
|
|
12
|
-
children,
|
|
13
|
-
title,
|
|
14
|
-
subtitle,
|
|
15
|
-
}: DashboardLayoutProps) {
|
|
16
|
-
return (
|
|
17
|
-
<div className="flex h-screen bg-gray-50">
|
|
18
|
-
{/* Sidebar */}
|
|
19
|
-
<Sidebar />
|
|
20
|
-
|
|
21
|
-
{/* Main content area */}
|
|
22
|
-
<div className="flex-1 flex flex-col overflow-hidden">
|
|
23
|
-
{/* Header */}
|
|
24
|
-
<DashboardHeader title={title} subtitle={subtitle} />
|
|
25
|
-
|
|
26
|
-
{/* Main content */}
|
|
27
|
-
<main
|
|
28
|
-
id="main-content"
|
|
29
|
-
role="main"
|
|
30
|
-
className="flex-1 overflow-y-auto p-6"
|
|
31
|
-
>
|
|
32
|
-
{children}
|
|
33
|
-
</main>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { Link, useLocation } from 'react-router-dom'
|
|
2
|
-
import { cn } from '@/lib/utils'
|
|
3
|
-
import {
|
|
4
|
-
Home,
|
|
5
|
-
LayoutDashboard,
|
|
6
|
-
Menu,
|
|
7
|
-
X
|
|
8
|
-
} from 'lucide-react'
|
|
9
|
-
import { useState } from 'react'
|
|
10
|
-
import { BluecopaLogo } from "@/components/ui/bluecopa-logo";
|
|
11
|
-
|
|
12
|
-
const navigation = [
|
|
13
|
-
{ name: 'Home', href: '/', icon: Home },
|
|
14
|
-
{ name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
export default function Navbar() {
|
|
18
|
-
const location = useLocation()
|
|
19
|
-
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<nav className="bg-[#041e42] border-b border-gray-200 sticky top-0 z-50">
|
|
23
|
-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
24
|
-
<div className="flex justify-between h-16">
|
|
25
|
-
<div className="flex">
|
|
26
|
-
{/* Logo */}
|
|
27
|
-
<div className="flex-shrink-0 flex items-center">
|
|
28
|
-
<Link to="/" className="flex items-center space-x-2">
|
|
29
|
-
<BluecopaLogo className="h-6 w-6 text-white" />
|
|
30
|
-
<span className="text-xl font-bold text-white">Bluecopa</span>
|
|
31
|
-
</Link>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
{/* Desktop Navigation */}
|
|
35
|
-
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
|
|
36
|
-
{navigation.map((item) => {
|
|
37
|
-
const isActive = location.pathname === item.href
|
|
38
|
-
return (
|
|
39
|
-
<Link
|
|
40
|
-
key={item.name}
|
|
41
|
-
to={item.href}
|
|
42
|
-
className={cn(
|
|
43
|
-
'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium transition-colors',
|
|
44
|
-
isActive
|
|
45
|
-
? 'border-blue-400 text-white'
|
|
46
|
-
: 'border-transparent text-blue-200 hover:border-blue-300 hover:text-white'
|
|
47
|
-
)}
|
|
48
|
-
>
|
|
49
|
-
<item.icon className="h-4 w-4 mr-2" />
|
|
50
|
-
{item.name}
|
|
51
|
-
</Link>
|
|
52
|
-
)
|
|
53
|
-
})}
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
{/* Mobile menu button */}
|
|
58
|
-
<div className="sm:hidden flex items-center">
|
|
59
|
-
<button
|
|
60
|
-
type="button"
|
|
61
|
-
className="inline-flex items-center justify-center p-2 rounded-md text-blue-200 hover:text-white hover:bg-blue-700"
|
|
62
|
-
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
63
|
-
aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
|
|
64
|
-
aria-expanded={mobileMenuOpen}
|
|
65
|
-
>
|
|
66
|
-
{mobileMenuOpen ? (
|
|
67
|
-
<X className="h-6 w-6" />
|
|
68
|
-
) : (
|
|
69
|
-
<Menu className="h-6 w-6" />
|
|
70
|
-
)}
|
|
71
|
-
</button>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
{/* Mobile menu */}
|
|
77
|
-
{mobileMenuOpen && (
|
|
78
|
-
<div className="sm:hidden">
|
|
79
|
-
<div className="pt-2 pb-3 space-y-1 bg-[#041e42]">
|
|
80
|
-
{navigation.map((item) => {
|
|
81
|
-
const isActive = location.pathname === item.href
|
|
82
|
-
return (
|
|
83
|
-
<Link
|
|
84
|
-
key={item.name}
|
|
85
|
-
to={item.href}
|
|
86
|
-
className={cn(
|
|
87
|
-
'block pl-3 pr-4 py-2 text-base font-medium transition-colors',
|
|
88
|
-
isActive
|
|
89
|
-
? 'bg-blue-700 border-r-4 border-blue-400 text-white'
|
|
90
|
-
: 'text-blue-200 hover:text-white hover:bg-blue-700'
|
|
91
|
-
)}
|
|
92
|
-
onClick={() => setMobileMenuOpen(false)}
|
|
93
|
-
>
|
|
94
|
-
<div className="flex items-center">
|
|
95
|
-
<item.icon className="h-5 w-5 mr-3" />
|
|
96
|
-
{item.name}
|
|
97
|
-
</div>
|
|
98
|
-
</Link>
|
|
99
|
-
)
|
|
100
|
-
})}
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
)}
|
|
104
|
-
</nav>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Link, useLocation } from "react-router-dom";
|
|
2
|
-
import { cn } from "@/lib/utils";
|
|
3
|
-
import { Home, LayoutDashboard } from "lucide-react";
|
|
4
|
-
import { BluecopaLogo } from "../ui/bluecopa-logo";
|
|
5
|
-
|
|
6
|
-
const navigation = [
|
|
7
|
-
{ name: "Home", href: "/", icon: Home },
|
|
8
|
-
{ name: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
export default function Sidebar() {
|
|
12
|
-
const location = useLocation();
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<div className="flex h-full w-64 flex-col bg-white border-r border-gray-200">
|
|
16
|
-
{/* Logo */}
|
|
17
|
-
<div className="flex h-16 items-center px-6 border-b border-gray-200">
|
|
18
|
-
<Link to="/" className="flex items-center space-x-2">
|
|
19
|
-
<BluecopaLogo className="h-6 w-6 text-white" />
|
|
20
|
-
<span className="text-xl font-bold text-gray-900">Bluecopa</span>
|
|
21
|
-
</Link>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
{/* Navigation */}
|
|
25
|
-
<nav className="flex-1 space-y-1 px-3 py-4" aria-label="Main navigation">
|
|
26
|
-
{navigation.map((item) => {
|
|
27
|
-
const isActive = location.pathname === item.href;
|
|
28
|
-
return (
|
|
29
|
-
<Link
|
|
30
|
-
key={item.name}
|
|
31
|
-
to={item.href}
|
|
32
|
-
className={cn(
|
|
33
|
-
"group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors",
|
|
34
|
-
isActive
|
|
35
|
-
? "bg-blue-50 text-blue-700 border-r-2 border-blue-600"
|
|
36
|
-
: "text-gray-700 hover:bg-gray-50 hover:text-gray-900"
|
|
37
|
-
)}
|
|
38
|
-
aria-current={isActive ? "page" : undefined}
|
|
39
|
-
>
|
|
40
|
-
<item.icon
|
|
41
|
-
className={cn(
|
|
42
|
-
"mr-3 h-5 w-5 flex-shrink-0",
|
|
43
|
-
isActive
|
|
44
|
-
? "text-blue-600"
|
|
45
|
-
: "text-gray-400 group-hover:text-gray-500"
|
|
46
|
-
)}
|
|
47
|
-
/>
|
|
48
|
-
{item.name}
|
|
49
|
-
</Link>
|
|
50
|
-
);
|
|
51
|
-
})}
|
|
52
|
-
</nav>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Card, CardContent, CardTitle } from '@/components/ui/card';
|
|
3
|
-
import { ArrowUpRight, ArrowDownRight } from 'lucide-react';
|
|
4
|
-
import { SparkAreaChart } from '@/components/charts/SparkAreaChart';
|
|
5
|
-
|
|
6
|
-
interface Metric {
|
|
7
|
-
title: string;
|
|
8
|
-
value: string;
|
|
9
|
-
change: string;
|
|
10
|
-
trend: 'up' | 'down';
|
|
11
|
-
icon: string;
|
|
12
|
-
color: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface DashboardMetricsProps {
|
|
16
|
-
ecommerceMetrics: Metric[];
|
|
17
|
-
monthlyRevenueData: any[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const iconMap: { [key: string]: React.ElementType } = {
|
|
21
|
-
DollarSign: ({ className }: { className?: string }) => (
|
|
22
|
-
<svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
23
|
-
<line x1="12" y1="1" x2="12" y2="23"></line>
|
|
24
|
-
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
|
25
|
-
</svg>
|
|
26
|
-
),
|
|
27
|
-
ShoppingCart: ({ className }: { className?: string }) => (
|
|
28
|
-
<svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
29
|
-
<circle cx="9" cy="21" r="1"></circle>
|
|
30
|
-
<circle cx="20" cy="21" r="1"></circle>
|
|
31
|
-
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
|
32
|
-
</svg>
|
|
33
|
-
),
|
|
34
|
-
TrendingUp: ({ className }: { className?: string }) => (
|
|
35
|
-
<svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
36
|
-
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
|
37
|
-
<polyline points="17 6 23 6 23 12"></polyline>
|
|
38
|
-
</svg>
|
|
39
|
-
),
|
|
40
|
-
Users: ({ className }: { className?: string }) => (
|
|
41
|
-
<svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
42
|
-
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
43
|
-
<circle cx="9" cy="7" r="4"></circle>
|
|
44
|
-
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
|
45
|
-
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
|
46
|
-
</svg>
|
|
47
|
-
)
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const DashboardMetrics: React.FC<DashboardMetricsProps> = ({
|
|
51
|
-
ecommerceMetrics,
|
|
52
|
-
monthlyRevenueData
|
|
53
|
-
}) => {
|
|
54
|
-
return (
|
|
55
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
56
|
-
{ecommerceMetrics.map((metric, index) => {
|
|
57
|
-
const IconComponent = iconMap[metric.icon];
|
|
58
|
-
return (
|
|
59
|
-
<Card key={index}>
|
|
60
|
-
<CardContent className="pt-6">
|
|
61
|
-
<div className="flex items-center justify-between space-y-0 pb-2">
|
|
62
|
-
<CardTitle className="text-sm font-medium text-gray-600">
|
|
63
|
-
{metric.title}
|
|
64
|
-
</CardTitle>
|
|
65
|
-
{IconComponent && <IconComponent className={`h-4 w-4 ${metric.color}`} />}
|
|
66
|
-
</div>
|
|
67
|
-
<div className="flex items-center space-x-2">
|
|
68
|
-
<div className="text-2xl font-bold">{metric.value}</div>
|
|
69
|
-
<div className={`flex items-center text-sm ${
|
|
70
|
-
metric.trend === 'up' ? 'text-green-600' : 'text-red-600'
|
|
71
|
-
}`}>
|
|
72
|
-
{metric.trend === 'up' ? (
|
|
73
|
-
<ArrowUpRight className="h-4 w-4" />
|
|
74
|
-
) : (
|
|
75
|
-
<ArrowDownRight className="h-4 w-4" />
|
|
76
|
-
)}
|
|
77
|
-
{metric.change}
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
{/* Mini trend chart */}
|
|
81
|
-
{monthlyRevenueData.length > 0 && (
|
|
82
|
-
<div className="mt-4">
|
|
83
|
-
<SparkAreaChart
|
|
84
|
-
data={monthlyRevenueData.slice(-6)} // Last 6 months
|
|
85
|
-
categories={[index === 0 ? "revenue" : "orders"]}
|
|
86
|
-
colors={[index === 0 ? "emerald" : index === 1 ? "blue" : index === 2 ? "violet" : "amber"]}
|
|
87
|
-
className="h-10 w-full"
|
|
88
|
-
/>
|
|
89
|
-
</div>
|
|
90
|
-
)}
|
|
91
|
-
</CardContent>
|
|
92
|
-
</Card>
|
|
93
|
-
);
|
|
94
|
-
})}
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
};
|