@moontra/moonui-pro 2.20.2 → 2.20.4
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/package.json +8 -3
- package/plugin/index.d.ts +86 -0
- package/plugin/index.js +308 -0
- package/scripts/postinstall.js +191 -23
- package/src/components/advanced-chart/index.tsx +0 -1246
- package/src/components/advanced-forms/index.tsx +0 -585
- package/src/components/animated-button/index.tsx +0 -385
- package/src/components/calendar/event-dialog.tsx +0 -377
- package/src/components/calendar/index.tsx +0 -1220
- package/src/components/calendar-pro/index.tsx +0 -1697
- package/src/components/color-picker/index.tsx +0 -432
- package/src/components/credit-card-input/index.tsx +0 -406
- package/src/components/dashboard/dashboard-grid.tsx +0 -480
- package/src/components/dashboard/demo.tsx +0 -425
- package/src/components/dashboard/index.tsx +0 -1046
- package/src/components/dashboard/time-range-picker.tsx +0 -336
- package/src/components/dashboard/types.ts +0 -225
- package/src/components/dashboard/widgets/activity-feed.tsx +0 -349
- package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
- package/src/components/dashboard/widgets/comparison-widget.tsx +0 -177
- package/src/components/dashboard/widgets/index.ts +0 -5
- package/src/components/dashboard/widgets/metric-card.tsx +0 -363
- package/src/components/dashboard/widgets/progress-widget.tsx +0 -113
- package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
- package/src/components/data-table/data-table-column-toggle.tsx +0 -169
- package/src/components/data-table/data-table-export.ts +0 -156
- package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
- package/src/components/data-table/index.tsx +0 -845
- package/src/components/draggable-list/index.tsx +0 -100
- package/src/components/error-boundary/index.tsx +0 -232
- package/src/components/file-upload/index.tsx +0 -1660
- package/src/components/floating-action-button/index.tsx +0 -206
- package/src/components/form-wizard/form-wizard-context.tsx +0 -335
- package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
- package/src/components/form-wizard/form-wizard-progress.tsx +0 -329
- package/src/components/form-wizard/form-wizard-step.tsx +0 -111
- package/src/components/form-wizard/index.tsx +0 -102
- package/src/components/form-wizard/types.ts +0 -77
- package/src/components/gesture-drawer/index.tsx +0 -551
- package/src/components/github-stars/github-api.ts +0 -426
- package/src/components/github-stars/hooks.ts +0 -517
- package/src/components/github-stars/index.tsx +0 -375
- package/src/components/github-stars/types.ts +0 -148
- package/src/components/github-stars/variants.tsx +0 -515
- package/src/components/health-check/index.tsx +0 -439
- package/src/components/hover-card-3d/index.tsx +0 -529
- package/src/components/index.ts +0 -130
- package/src/components/internal/index.ts +0 -78
- package/src/components/kanban/add-card-modal.tsx +0 -502
- package/src/components/kanban/card-detail-modal.tsx +0 -761
- package/src/components/kanban/index.ts +0 -13
- package/src/components/kanban/kanban.tsx +0 -1689
- package/src/components/kanban/types.ts +0 -168
- package/src/components/lazy-component/index.tsx +0 -823
- package/src/components/license-error/index.tsx +0 -31
- package/src/components/magnetic-button/index.tsx +0 -216
- package/src/components/memory-efficient-data/index.tsx +0 -1018
- package/src/components/moonui-quiz-form/index.tsx +0 -817
- package/src/components/navbar/index.tsx +0 -781
- package/src/components/optimized-image/index.tsx +0 -425
- package/src/components/performance-debugger/index.tsx +0 -613
- package/src/components/performance-monitor/index.tsx +0 -808
- package/src/components/phone-number-input/index.tsx +0 -343
- package/src/components/phone-number-input/phone-number-input-simple.tsx +0 -167
- package/src/components/pinch-zoom/index.tsx +0 -566
- package/src/components/quiz-form/index.tsx +0 -479
- package/src/components/rich-text-editor/index.tsx +0 -2322
- package/src/components/rich-text-editor/slash-commands-extension.ts +0 -230
- package/src/components/rich-text-editor/slash-commands.css +0 -35
- package/src/components/rich-text-editor/table-styles.css +0 -65
- package/src/components/sidebar/index.tsx +0 -884
- package/src/components/spotlight-card/index.tsx +0 -191
- package/src/components/swipeable-card/index.tsx +0 -100
- package/src/components/timeline/index.tsx +0 -1183
- package/src/components/ui/accordion.tsx +0 -581
- package/src/components/ui/alert-dialog.tsx +0 -141
- package/src/components/ui/alert.tsx +0 -141
- package/src/components/ui/aspect-ratio.tsx +0 -245
- package/src/components/ui/avatar.tsx +0 -155
- package/src/components/ui/badge.tsx +0 -230
- package/src/components/ui/breadcrumb.tsx +0 -216
- package/src/components/ui/button.tsx +0 -228
- package/src/components/ui/calendar.tsx +0 -387
- package/src/components/ui/card.tsx +0 -216
- package/src/components/ui/checkbox.tsx +0 -259
- package/src/components/ui/collapsible.tsx +0 -631
- package/src/components/ui/color-picker.tsx +0 -97
- package/src/components/ui/command.tsx +0 -948
- package/src/components/ui/dialog.tsx +0 -752
- package/src/components/ui/dropdown-menu.tsx +0 -706
- package/src/components/ui/gesture-drawer.tsx +0 -11
- package/src/components/ui/hover-card.tsx +0 -29
- package/src/components/ui/index.ts +0 -222
- package/src/components/ui/input.tsx +0 -224
- package/src/components/ui/label.tsx +0 -29
- package/src/components/ui/lightbox.tsx +0 -606
- package/src/components/ui/magnetic-button.tsx +0 -129
- package/src/components/ui/media-gallery.tsx +0 -611
- package/src/components/ui/navigation-menu.tsx +0 -130
- package/src/components/ui/pagination.tsx +0 -125
- package/src/components/ui/popover.tsx +0 -185
- package/src/components/ui/progress.tsx +0 -30
- package/src/components/ui/radio-group.tsx +0 -257
- package/src/components/ui/scroll-area.tsx +0 -47
- package/src/components/ui/select.tsx +0 -378
- package/src/components/ui/separator.tsx +0 -145
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/skeleton.tsx +0 -20
- package/src/components/ui/slider.tsx +0 -354
- package/src/components/ui/spotlight-card.tsx +0 -119
- package/src/components/ui/switch.tsx +0 -86
- package/src/components/ui/table.tsx +0 -331
- package/src/components/ui/tabs-pro.tsx +0 -542
- package/src/components/ui/tabs.tsx +0 -54
- package/src/components/ui/textarea.tsx +0 -28
- package/src/components/ui/toast.tsx +0 -317
- package/src/components/ui/toggle.tsx +0 -119
- package/src/components/ui/tooltip.tsx +0 -151
- package/src/components/virtual-list/index.tsx +0 -668
- package/src/hooks/use-chart.ts +0 -205
- package/src/hooks/use-data-table.ts +0 -182
- package/src/hooks/use-docs-pro-access.ts +0 -13
- package/src/hooks/use-license-check.ts +0 -65
- package/src/hooks/use-subscription.ts +0 -19
- package/src/hooks/use-toast.ts +0 -15
- package/src/index.ts +0 -22
- package/src/lib/ai-providers.ts +0 -377
- package/src/lib/component-metadata.ts +0 -18
- package/src/lib/micro-interactions.ts +0 -255
- package/src/lib/paddle.ts +0 -17
- package/src/lib/utils.ts +0 -6
- package/src/patterns/login-form/index.tsx +0 -276
- package/src/patterns/login-form/types.ts +0 -67
- package/src/setupTests.ts +0 -41
- package/src/styles/advanced-chart.css +0 -239
- package/src/styles/calendar.css +0 -35
- package/src/styles/design-system.css +0 -363
- package/src/styles/index.css +0 -681
- package/src/styles/tailwind.css +0 -7
- package/src/styles/tokens.css +0 -455
- package/src/types/next-auth.d.ts +0 -21
- package/src/use-intersection-observer.tsx +0 -154
- package/src/use-local-storage.tsx +0 -71
- package/src/use-paddle.ts +0 -138
- package/src/use-performance-optimizer.ts +0 -389
- package/src/use-pro-access.ts +0 -141
- package/src/use-scroll-animation.ts +0 -219
- package/src/use-subscription.ts +0 -37
- package/src/use-toast.ts +0 -32
- package/src/utils/chart-helpers.ts +0 -357
- package/src/utils/cn.ts +0 -6
- package/src/utils/data-processing.ts +0 -151
- package/src/utils/license-validator.tsx +0 -183
|
@@ -1,1046 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { motion, AnimatePresence } from 'framer-motion'
|
|
5
|
-
import { Button } from '../ui/button'
|
|
6
|
-
import { Badge } from '../ui/badge'
|
|
7
|
-
import { Input } from '../ui/input'
|
|
8
|
-
import { ScrollArea } from '../ui/scroll-area'
|
|
9
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'
|
|
10
|
-
import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'
|
|
11
|
-
import { Separator } from '../ui/separator'
|
|
12
|
-
import {
|
|
13
|
-
Activity,
|
|
14
|
-
Download,
|
|
15
|
-
Upload,
|
|
16
|
-
Settings,
|
|
17
|
-
Edit3,
|
|
18
|
-
Save,
|
|
19
|
-
X,
|
|
20
|
-
Plus,
|
|
21
|
-
RefreshCw,
|
|
22
|
-
Share2,
|
|
23
|
-
Filter,
|
|
24
|
-
LayoutGrid,
|
|
25
|
-
Palette,
|
|
26
|
-
Search,
|
|
27
|
-
Bell,
|
|
28
|
-
Menu,
|
|
29
|
-
ChevronRight,
|
|
30
|
-
Sparkles,
|
|
31
|
-
BarChart3,
|
|
32
|
-
Users,
|
|
33
|
-
DollarSign,
|
|
34
|
-
TrendingUp,
|
|
35
|
-
Calendar,
|
|
36
|
-
Map,
|
|
37
|
-
Table,
|
|
38
|
-
Clock,
|
|
39
|
-
Target,
|
|
40
|
-
ArrowUpRight,
|
|
41
|
-
CheckCircle,
|
|
42
|
-
User,
|
|
43
|
-
LogOut,
|
|
44
|
-
Settings2,
|
|
45
|
-
AlertCircle,
|
|
46
|
-
Info,
|
|
47
|
-
CheckCheck
|
|
48
|
-
} from 'lucide-react'
|
|
49
|
-
import { cn } from '../../lib/utils'
|
|
50
|
-
import { DashboardGrid } from './dashboard-grid'
|
|
51
|
-
import { TimeRangePicker } from './time-range-picker'
|
|
52
|
-
import { MetricCard } from './widgets/metric-card'
|
|
53
|
-
import { ChartWidget } from './widgets/chart-widget'
|
|
54
|
-
import { ActivityFeed } from './widgets/activity-feed'
|
|
55
|
-
import {
|
|
56
|
-
Widget,
|
|
57
|
-
DashboardConfig,
|
|
58
|
-
DashboardTheme,
|
|
59
|
-
TimeRange,
|
|
60
|
-
MetricData,
|
|
61
|
-
ChartData,
|
|
62
|
-
ActivityItem,
|
|
63
|
-
DashboardTemplate,
|
|
64
|
-
WidgetType,
|
|
65
|
-
DashboardNotification
|
|
66
|
-
} from './types'
|
|
67
|
-
import {
|
|
68
|
-
DropdownMenu,
|
|
69
|
-
DropdownMenuContent,
|
|
70
|
-
DropdownMenuItem,
|
|
71
|
-
DropdownMenuSeparator,
|
|
72
|
-
DropdownMenuSub,
|
|
73
|
-
DropdownMenuSubContent,
|
|
74
|
-
DropdownMenuSubTrigger,
|
|
75
|
-
DropdownMenuTrigger,
|
|
76
|
-
} from '../ui/dropdown-menu'
|
|
77
|
-
import {
|
|
78
|
-
Dialog,
|
|
79
|
-
DialogContent,
|
|
80
|
-
DialogDescription,
|
|
81
|
-
DialogHeader,
|
|
82
|
-
DialogTitle,
|
|
83
|
-
DialogTrigger,
|
|
84
|
-
} from '../ui/dialog'
|
|
85
|
-
import {
|
|
86
|
-
Sheet,
|
|
87
|
-
SheetContent,
|
|
88
|
-
SheetDescription,
|
|
89
|
-
SheetHeader,
|
|
90
|
-
SheetTitle,
|
|
91
|
-
SheetTrigger,
|
|
92
|
-
} from '../ui/sheet'
|
|
93
|
-
|
|
94
|
-
interface DashboardProps {
|
|
95
|
-
config?: DashboardConfig
|
|
96
|
-
widgets?: Widget[]
|
|
97
|
-
templates?: DashboardTemplate[]
|
|
98
|
-
onConfigChange?: (config: DashboardConfig) => void
|
|
99
|
-
onWidgetAdd?: (widget: Omit<Widget, 'id'>) => void
|
|
100
|
-
onWidgetRemove?: (widgetId: string) => void
|
|
101
|
-
onWidgetUpdate?: (widgetId: string, updates: Partial<Widget>) => void
|
|
102
|
-
onExport?: (format: 'json' | 'pdf' | 'png') => void
|
|
103
|
-
onImport?: (file: File) => void
|
|
104
|
-
className?: string
|
|
105
|
-
showHeader?: boolean
|
|
106
|
-
title?: string
|
|
107
|
-
description?: string
|
|
108
|
-
editable?: boolean
|
|
109
|
-
realtime?: boolean
|
|
110
|
-
glassmorphism?: boolean
|
|
111
|
-
|
|
112
|
-
// Notification yönetimi
|
|
113
|
-
notifications?: DashboardNotification[]
|
|
114
|
-
onNotificationClick?: (notification: DashboardNotification) => void
|
|
115
|
-
onNotificationMarkAsRead?: (notificationId: string) => void
|
|
116
|
-
onNotificationMarkAllAsRead?: () => void
|
|
117
|
-
onNotificationClear?: (notificationId: string) => void
|
|
118
|
-
onNotificationClearAll?: () => void
|
|
119
|
-
|
|
120
|
-
// User yönetimi
|
|
121
|
-
user?: {
|
|
122
|
-
name: string
|
|
123
|
-
email?: string
|
|
124
|
-
avatar?: string
|
|
125
|
-
role?: string
|
|
126
|
-
}
|
|
127
|
-
userMenuItems?: Array<{
|
|
128
|
-
id: string
|
|
129
|
-
label: string
|
|
130
|
-
icon?: React.ReactNode
|
|
131
|
-
onClick?: () => void
|
|
132
|
-
separator?: boolean
|
|
133
|
-
}>
|
|
134
|
-
onUserMenuClick?: () => void
|
|
135
|
-
onProfileClick?: () => void
|
|
136
|
-
onLogout?: () => void
|
|
137
|
-
|
|
138
|
-
// Header actions
|
|
139
|
-
onSearch?: (query: string) => void
|
|
140
|
-
onThemeChange?: (theme: DashboardTheme) => void
|
|
141
|
-
onMenuClick?: () => void
|
|
142
|
-
onRefresh?: () => void
|
|
143
|
-
|
|
144
|
-
// Custom header actions
|
|
145
|
-
headerActions?: React.ReactNode
|
|
146
|
-
|
|
147
|
-
// Time range yönetimi
|
|
148
|
-
timeRange?: TimeRange
|
|
149
|
-
onTimeRangeChange?: (range: TimeRange) => void
|
|
150
|
-
|
|
151
|
-
// Custom branding
|
|
152
|
-
logo?: React.ReactNode
|
|
153
|
-
brandName?: string
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Dashboard template'leri
|
|
157
|
-
const DASHBOARD_TEMPLATES: DashboardTemplate[] = [
|
|
158
|
-
{
|
|
159
|
-
id: 'analytics',
|
|
160
|
-
name: 'Analytics Dashboard',
|
|
161
|
-
description: 'Comprehensive analytics with metrics and charts',
|
|
162
|
-
category: 'analytics',
|
|
163
|
-
theme: 'analytics',
|
|
164
|
-
widgets: [
|
|
165
|
-
{
|
|
166
|
-
type: 'metric',
|
|
167
|
-
title: 'Total Revenue',
|
|
168
|
-
size: { w: 3, h: 2 },
|
|
169
|
-
position: { x: 0, y: 0 },
|
|
170
|
-
data: {
|
|
171
|
-
id: 'revenue',
|
|
172
|
-
title: 'Total Revenue',
|
|
173
|
-
value: 125430,
|
|
174
|
-
change: { value: 12, type: 'increase', period: 'last month' },
|
|
175
|
-
color: 'success',
|
|
176
|
-
icon: <DollarSign className="h-4 w-4" />,
|
|
177
|
-
sparkline: [100, 120, 115, 125, 130, 128, 132],
|
|
178
|
-
unit: '$'
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
type: 'metric',
|
|
183
|
-
title: 'Active Users',
|
|
184
|
-
size: { w: 3, h: 2 },
|
|
185
|
-
position: { x: 3, y: 0 },
|
|
186
|
-
data: {
|
|
187
|
-
id: 'users',
|
|
188
|
-
title: 'Active Users',
|
|
189
|
-
value: 2543,
|
|
190
|
-
change: { value: 8, type: 'increase', period: 'last week' },
|
|
191
|
-
color: 'primary',
|
|
192
|
-
icon: <Users className="h-4 w-4" />,
|
|
193
|
-
sparkline: [200, 220, 210, 230, 225, 240, 254]
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
type: 'chart',
|
|
198
|
-
title: 'Revenue Trend',
|
|
199
|
-
size: { w: 6, h: 4 },
|
|
200
|
-
position: { x: 0, y: 2 },
|
|
201
|
-
data: {
|
|
202
|
-
type: 'area',
|
|
203
|
-
data: [
|
|
204
|
-
{ name: 'Jan', revenue: 4000, profit: 2400 },
|
|
205
|
-
{ name: 'Feb', revenue: 3000, profit: 1398 },
|
|
206
|
-
{ name: 'Mar', revenue: 5000, profit: 3200 },
|
|
207
|
-
{ name: 'Apr', revenue: 4500, profit: 2900 },
|
|
208
|
-
{ name: 'May', revenue: 6000, profit: 3800 },
|
|
209
|
-
{ name: 'Jun', revenue: 5500, profit: 3400 }
|
|
210
|
-
]
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
type: 'activity',
|
|
215
|
-
title: 'Recent Activity',
|
|
216
|
-
size: { w: 3, h: 6 },
|
|
217
|
-
position: { x: 9, y: 0 },
|
|
218
|
-
data: {
|
|
219
|
-
realtime: true,
|
|
220
|
-
items: [
|
|
221
|
-
{
|
|
222
|
-
id: '1',
|
|
223
|
-
type: 'success',
|
|
224
|
-
title: 'completed a purchase',
|
|
225
|
-
description: 'Order #12345 - $250.00',
|
|
226
|
-
timestamp: new Date(Date.now() - 1000 * 60 * 5),
|
|
227
|
-
user: { name: 'John Doe' }
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
id: '2',
|
|
231
|
-
type: 'info',
|
|
232
|
-
title: 'signed up',
|
|
233
|
-
description: 'New user registration',
|
|
234
|
-
timestamp: new Date(Date.now() - 1000 * 60 * 15),
|
|
235
|
-
user: { name: 'Jane Smith' }
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
]
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
id: 'sales',
|
|
244
|
-
name: 'Sales Dashboard',
|
|
245
|
-
description: 'Track sales performance and targets',
|
|
246
|
-
category: 'sales',
|
|
247
|
-
theme: 'sales',
|
|
248
|
-
widgets: [
|
|
249
|
-
// Sales-specific widgets
|
|
250
|
-
]
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
id: 'monitoring',
|
|
254
|
-
name: 'System Monitoring',
|
|
255
|
-
description: 'Monitor system health and performance',
|
|
256
|
-
category: 'operations',
|
|
257
|
-
theme: 'monitoring',
|
|
258
|
-
widgets: [
|
|
259
|
-
// Monitoring widgets
|
|
260
|
-
]
|
|
261
|
-
}
|
|
262
|
-
]
|
|
263
|
-
|
|
264
|
-
// Widget türleri için ikonlar
|
|
265
|
-
const WIDGET_TYPE_ICONS: Record<WidgetType, React.ReactNode> = {
|
|
266
|
-
metric: <BarChart3 className="h-4 w-4" />,
|
|
267
|
-
chart: <Activity className="h-4 w-4" />,
|
|
268
|
-
table: <Table className="h-4 w-4" />,
|
|
269
|
-
map: <Map className="h-4 w-4" />,
|
|
270
|
-
activity: <Clock className="h-4 w-4" />,
|
|
271
|
-
calendar: <Calendar className="h-4 w-4" />,
|
|
272
|
-
progress: <Target className="h-4 w-4" />,
|
|
273
|
-
comparison: <TrendingUp className="h-4 w-4" />
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Tema renkleri
|
|
277
|
-
const THEME_COLORS: Record<DashboardTheme, string> = {
|
|
278
|
-
analytics: 'from-blue-500/10 to-purple-500/10',
|
|
279
|
-
sales: 'from-green-500/10 to-emerald-500/10',
|
|
280
|
-
monitoring: 'from-orange-500/10 to-red-500/10',
|
|
281
|
-
finance: 'from-indigo-500/10 to-blue-500/10',
|
|
282
|
-
custom: 'from-gray-500/10 to-gray-600/10'
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Relative time formatter
|
|
286
|
-
function formatRelativeTime(date: Date): string {
|
|
287
|
-
const now = new Date();
|
|
288
|
-
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
|
289
|
-
|
|
290
|
-
if (diffInSeconds < 60) return 'just now';
|
|
291
|
-
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
|
|
292
|
-
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
|
|
293
|
-
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
|
|
294
|
-
|
|
295
|
-
return date.toLocaleDateString();
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function Dashboard({
|
|
299
|
-
config,
|
|
300
|
-
widgets: initialWidgets = [],
|
|
301
|
-
templates = DASHBOARD_TEMPLATES,
|
|
302
|
-
onConfigChange,
|
|
303
|
-
onWidgetAdd,
|
|
304
|
-
onWidgetRemove,
|
|
305
|
-
onWidgetUpdate,
|
|
306
|
-
onExport,
|
|
307
|
-
onImport,
|
|
308
|
-
className,
|
|
309
|
-
showHeader = true,
|
|
310
|
-
title = 'Dashboard',
|
|
311
|
-
description = 'Real-time analytics and insights',
|
|
312
|
-
editable = true,
|
|
313
|
-
realtime = false,
|
|
314
|
-
glassmorphism = true,
|
|
315
|
-
notifications = [],
|
|
316
|
-
onNotificationClick,
|
|
317
|
-
onNotificationMarkAsRead,
|
|
318
|
-
onNotificationMarkAllAsRead,
|
|
319
|
-
onNotificationClear,
|
|
320
|
-
onNotificationClearAll,
|
|
321
|
-
user,
|
|
322
|
-
userMenuItems,
|
|
323
|
-
onUserMenuClick,
|
|
324
|
-
onProfileClick,
|
|
325
|
-
onLogout,
|
|
326
|
-
onSearch,
|
|
327
|
-
onThemeChange,
|
|
328
|
-
onMenuClick,
|
|
329
|
-
onRefresh,
|
|
330
|
-
headerActions,
|
|
331
|
-
timeRange: propTimeRange,
|
|
332
|
-
onTimeRangeChange,
|
|
333
|
-
logo,
|
|
334
|
-
brandName
|
|
335
|
-
}: DashboardProps) {
|
|
336
|
-
// State yönetimi
|
|
337
|
-
const [editMode, setEditMode] = React.useState(false)
|
|
338
|
-
const [widgets, setWidgets] = React.useState<Widget[]>(initialWidgets)
|
|
339
|
-
const [selectedTheme, setSelectedTheme] = React.useState<DashboardTheme>('analytics')
|
|
340
|
-
const [timeRange, setTimeRange] = React.useState<TimeRange | undefined>(propTimeRange)
|
|
341
|
-
const [searchQuery, setSearchQuery] = React.useState('')
|
|
342
|
-
const [showWidgetLibrary, setShowWidgetLibrary] = React.useState(false)
|
|
343
|
-
const [showTemplates, setShowTemplates] = React.useState(false)
|
|
344
|
-
const [refreshing, setRefreshing] = React.useState(false)
|
|
345
|
-
const [showNotifications, setShowNotifications] = React.useState(false)
|
|
346
|
-
|
|
347
|
-
// initialWidgets değiştiğinde state'i güncelle
|
|
348
|
-
React.useEffect(() => {
|
|
349
|
-
setWidgets(initialWidgets)
|
|
350
|
-
}, [initialWidgets])
|
|
351
|
-
|
|
352
|
-
// propTimeRange değiştiğinde state'i güncelle
|
|
353
|
-
React.useEffect(() => {
|
|
354
|
-
if (propTimeRange) {
|
|
355
|
-
setTimeRange(propTimeRange)
|
|
356
|
-
}
|
|
357
|
-
}, [propTimeRange])
|
|
358
|
-
|
|
359
|
-
// WebSocket bağlantısı (real-time için)
|
|
360
|
-
React.useEffect(() => {
|
|
361
|
-
if (!realtime) return
|
|
362
|
-
|
|
363
|
-
// Simüle edilmiş WebSocket bağlantısı
|
|
364
|
-
const interval = setInterval(() => {
|
|
365
|
-
// Widget'ları güncelle
|
|
366
|
-
setWidgets(prev => prev.map(widget => {
|
|
367
|
-
if (widget.type === 'metric' && widget.data) {
|
|
368
|
-
const newValue = typeof widget.data.value === 'number'
|
|
369
|
-
? widget.data.value + Math.floor(Math.random() * 10 - 5)
|
|
370
|
-
: widget.data.value
|
|
371
|
-
|
|
372
|
-
return {
|
|
373
|
-
...widget,
|
|
374
|
-
data: {
|
|
375
|
-
...widget.data,
|
|
376
|
-
value: newValue,
|
|
377
|
-
lastUpdated: new Date()
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return widget
|
|
382
|
-
}))
|
|
383
|
-
}, 5000)
|
|
384
|
-
|
|
385
|
-
return () => clearInterval(interval)
|
|
386
|
-
}, [realtime])
|
|
387
|
-
|
|
388
|
-
// Widget ekle
|
|
389
|
-
const handleAddWidget = (type: WidgetType) => {
|
|
390
|
-
const newWidget: Widget = {
|
|
391
|
-
id: `widget-${Date.now()}`,
|
|
392
|
-
type,
|
|
393
|
-
title: `New ${type} Widget`,
|
|
394
|
-
size: { w: 3, h: 3 },
|
|
395
|
-
position: { x: 0, y: 0 },
|
|
396
|
-
data: generateDefaultData(type)
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
setWidgets(prev => [...prev, newWidget])
|
|
400
|
-
onWidgetAdd?.(newWidget)
|
|
401
|
-
setShowWidgetLibrary(false)
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Template uygula
|
|
405
|
-
const applyTemplate = (template: DashboardTemplate) => {
|
|
406
|
-
const newWidgets = template.widgets.map((w, index) => ({
|
|
407
|
-
...w,
|
|
408
|
-
id: `widget-${Date.now()}-${index}`,
|
|
409
|
-
position: w.position || { x: (index * 3) % 12, y: Math.floor((index * 3) / 12) * 3 },
|
|
410
|
-
size: w.size || { w: 3, h: 3 }
|
|
411
|
-
}))
|
|
412
|
-
|
|
413
|
-
setWidgets(newWidgets as Widget[])
|
|
414
|
-
setSelectedTheme(template.theme)
|
|
415
|
-
setShowTemplates(false)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Varsayılan widget verisi oluştur
|
|
419
|
-
const generateDefaultData = (type: WidgetType): any => {
|
|
420
|
-
switch (type) {
|
|
421
|
-
case 'metric':
|
|
422
|
-
return {
|
|
423
|
-
id: 'new-metric',
|
|
424
|
-
title: 'New Metric',
|
|
425
|
-
value: 0,
|
|
426
|
-
icon: <BarChart3 className="h-4 w-4" />,
|
|
427
|
-
color: 'primary'
|
|
428
|
-
}
|
|
429
|
-
case 'chart':
|
|
430
|
-
return {
|
|
431
|
-
type: 'line',
|
|
432
|
-
data: [
|
|
433
|
-
{ name: 'Mon', value: 100 },
|
|
434
|
-
{ name: 'Tue', value: 120 },
|
|
435
|
-
{ name: 'Wed', value: 110 },
|
|
436
|
-
{ name: 'Thu', value: 130 },
|
|
437
|
-
{ name: 'Fri', value: 125 }
|
|
438
|
-
]
|
|
439
|
-
}
|
|
440
|
-
case 'activity':
|
|
441
|
-
return {
|
|
442
|
-
items: [],
|
|
443
|
-
realtime: true
|
|
444
|
-
}
|
|
445
|
-
default:
|
|
446
|
-
return {}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Widget kütüphanesi
|
|
451
|
-
const WidgetLibrary = () => (
|
|
452
|
-
<Sheet open={showWidgetLibrary} onOpenChange={setShowWidgetLibrary}>
|
|
453
|
-
<SheetContent className={cn(
|
|
454
|
-
"w-[400px] sm:w-[540px]",
|
|
455
|
-
glassmorphism && "bg-background/95 backdrop-blur-md"
|
|
456
|
-
)}>
|
|
457
|
-
<SheetHeader>
|
|
458
|
-
<SheetTitle>Widget Library</SheetTitle>
|
|
459
|
-
<SheetDescription>
|
|
460
|
-
Add widgets to customize your dashboard
|
|
461
|
-
</SheetDescription>
|
|
462
|
-
</SheetHeader>
|
|
463
|
-
|
|
464
|
-
<div className="mt-6 space-y-4">
|
|
465
|
-
<div className="relative">
|
|
466
|
-
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
|
467
|
-
<Input
|
|
468
|
-
placeholder="Search widgets..."
|
|
469
|
-
value={searchQuery}
|
|
470
|
-
onChange={(e) => {
|
|
471
|
-
setSearchQuery(e.target.value);
|
|
472
|
-
onSearch?.(e.target.value);
|
|
473
|
-
}}
|
|
474
|
-
className="pl-10"
|
|
475
|
-
/>
|
|
476
|
-
</div>
|
|
477
|
-
|
|
478
|
-
<ScrollArea className="h-[calc(100vh-200px)]">
|
|
479
|
-
<div className="grid grid-cols-2 gap-4">
|
|
480
|
-
{Object.entries(WIDGET_TYPE_ICONS)
|
|
481
|
-
.filter(([type]) => type.toLowerCase().includes(searchQuery.toLowerCase()))
|
|
482
|
-
.map(([type, icon]) => (
|
|
483
|
-
<motion.div
|
|
484
|
-
key={type}
|
|
485
|
-
whileHover={{ scale: 1.02 }}
|
|
486
|
-
whileTap={{ scale: 0.98 }}
|
|
487
|
-
>
|
|
488
|
-
<Button
|
|
489
|
-
variant="outline"
|
|
490
|
-
className="w-full h-24 flex-col gap-2"
|
|
491
|
-
onClick={() => handleAddWidget(type as WidgetType)}
|
|
492
|
-
>
|
|
493
|
-
<div className="p-2 rounded-lg bg-muted">
|
|
494
|
-
{icon}
|
|
495
|
-
</div>
|
|
496
|
-
<span className="capitalize text-sm">{type}</span>
|
|
497
|
-
</Button>
|
|
498
|
-
</motion.div>
|
|
499
|
-
))}
|
|
500
|
-
</div>
|
|
501
|
-
</ScrollArea>
|
|
502
|
-
</div>
|
|
503
|
-
</SheetContent>
|
|
504
|
-
</Sheet>
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
// Template galerisi
|
|
508
|
-
const TemplateGallery = () => (
|
|
509
|
-
<Dialog open={showTemplates} onOpenChange={setShowTemplates}>
|
|
510
|
-
<DialogContent className={cn(
|
|
511
|
-
"max-w-4xl",
|
|
512
|
-
glassmorphism && "bg-background/95 backdrop-blur-md"
|
|
513
|
-
)}>
|
|
514
|
-
<DialogHeader>
|
|
515
|
-
<DialogTitle>Dashboard Templates</DialogTitle>
|
|
516
|
-
<DialogDescription>
|
|
517
|
-
Start with a pre-built template or create your own
|
|
518
|
-
</DialogDescription>
|
|
519
|
-
</DialogHeader>
|
|
520
|
-
|
|
521
|
-
<Tabs defaultValue="all" className="mt-4">
|
|
522
|
-
<TabsList>
|
|
523
|
-
<TabsTrigger value="all">All Templates</TabsTrigger>
|
|
524
|
-
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
525
|
-
<TabsTrigger value="sales">Sales</TabsTrigger>
|
|
526
|
-
<TabsTrigger value="operations">Operations</TabsTrigger>
|
|
527
|
-
</TabsList>
|
|
528
|
-
|
|
529
|
-
<TabsContent value="all" className="mt-4">
|
|
530
|
-
<div className="grid grid-cols-3 gap-4">
|
|
531
|
-
{templates.map((template) => (
|
|
532
|
-
<motion.div
|
|
533
|
-
key={template.id}
|
|
534
|
-
whileHover={{ y: -4 }}
|
|
535
|
-
className="cursor-pointer"
|
|
536
|
-
onClick={() => applyTemplate(template)}
|
|
537
|
-
>
|
|
538
|
-
<div className={cn(
|
|
539
|
-
"relative overflow-hidden rounded-lg border p-4 hover:shadow-lg transition-all",
|
|
540
|
-
"bg-gradient-to-br", THEME_COLORS[template.theme]
|
|
541
|
-
)}>
|
|
542
|
-
{template.isPro && (
|
|
543
|
-
<Badge className="absolute top-2 right-2" variant="secondary">
|
|
544
|
-
PRO
|
|
545
|
-
</Badge>
|
|
546
|
-
)}
|
|
547
|
-
<h3 className="font-semibold mb-1">{template.name}</h3>
|
|
548
|
-
<p className="text-sm text-muted-foreground">
|
|
549
|
-
{template.description}
|
|
550
|
-
</p>
|
|
551
|
-
<div className="mt-3 flex items-center gap-2 text-xs text-muted-foreground">
|
|
552
|
-
<span>{template.widgets.length} widgets</span>
|
|
553
|
-
<span>•</span>
|
|
554
|
-
<span className="capitalize">{template.category}</span>
|
|
555
|
-
</div>
|
|
556
|
-
</div>
|
|
557
|
-
</motion.div>
|
|
558
|
-
))}
|
|
559
|
-
</div>
|
|
560
|
-
</TabsContent>
|
|
561
|
-
</Tabs>
|
|
562
|
-
</DialogContent>
|
|
563
|
-
</Dialog>
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
return (
|
|
567
|
-
<motion.div
|
|
568
|
-
className={cn(
|
|
569
|
-
"w-full min-h-screen",
|
|
570
|
-
glassmorphism && "bg-gradient-to-br",
|
|
571
|
-
glassmorphism && THEME_COLORS[selectedTheme],
|
|
572
|
-
className
|
|
573
|
-
)}
|
|
574
|
-
initial={{ opacity: 0 }}
|
|
575
|
-
animate={{ opacity: 1 }}
|
|
576
|
-
transition={{ duration: 0.5 }}
|
|
577
|
-
>
|
|
578
|
-
{/* Header */}
|
|
579
|
-
{showHeader && (
|
|
580
|
-
<motion.div
|
|
581
|
-
className={cn(
|
|
582
|
-
"sticky top-0 z-30 border-b",
|
|
583
|
-
glassmorphism ? "bg-background/80 backdrop-blur-md" : "bg-background"
|
|
584
|
-
)}
|
|
585
|
-
initial={{ y: -50 }}
|
|
586
|
-
animate={{ y: 0 }}
|
|
587
|
-
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
|
588
|
-
>
|
|
589
|
-
<div className="px-6 py-4">
|
|
590
|
-
<div className="flex items-center justify-between">
|
|
591
|
-
<div className="flex items-center gap-4">
|
|
592
|
-
<motion.div
|
|
593
|
-
whileHover={{ scale: 1.05 }}
|
|
594
|
-
whileTap={{ scale: 0.95 }}
|
|
595
|
-
>
|
|
596
|
-
<Button
|
|
597
|
-
variant="ghost"
|
|
598
|
-
size="sm"
|
|
599
|
-
className="lg:hidden"
|
|
600
|
-
onClick={onMenuClick}
|
|
601
|
-
>
|
|
602
|
-
<Menu className="h-5 w-5" />
|
|
603
|
-
</Button>
|
|
604
|
-
</motion.div>
|
|
605
|
-
|
|
606
|
-
{/* Logo and Brand */}
|
|
607
|
-
{(logo || brandName) && (
|
|
608
|
-
<div className="flex items-center gap-2">
|
|
609
|
-
{logo}
|
|
610
|
-
{brandName && (
|
|
611
|
-
<span className="font-semibold text-lg">{brandName}</span>
|
|
612
|
-
)}
|
|
613
|
-
</div>
|
|
614
|
-
)}
|
|
615
|
-
|
|
616
|
-
<div>
|
|
617
|
-
<div className="flex items-center gap-2">
|
|
618
|
-
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
|
|
619
|
-
{realtime && (
|
|
620
|
-
<Badge variant="secondary" className="animate-pulse">
|
|
621
|
-
<span className="mr-1 h-1.5 w-1.5 rounded-full bg-green-500" />
|
|
622
|
-
Real-time
|
|
623
|
-
</Badge>
|
|
624
|
-
)}
|
|
625
|
-
</div>
|
|
626
|
-
<p className="text-sm text-muted-foreground">{description}</p>
|
|
627
|
-
</div>
|
|
628
|
-
</div>
|
|
629
|
-
|
|
630
|
-
{/* Header actions */}
|
|
631
|
-
<div className="flex items-center gap-2">
|
|
632
|
-
{/* Time range picker */}
|
|
633
|
-
<TimeRangePicker
|
|
634
|
-
value={timeRange}
|
|
635
|
-
onChange={(range) => {
|
|
636
|
-
setTimeRange(range);
|
|
637
|
-
onTimeRangeChange?.(range);
|
|
638
|
-
}}
|
|
639
|
-
glassmorphism={glassmorphism}
|
|
640
|
-
/>
|
|
641
|
-
|
|
642
|
-
{/* Notifications */}
|
|
643
|
-
<DropdownMenu open={showNotifications} onOpenChange={setShowNotifications}>
|
|
644
|
-
<DropdownMenuTrigger asChild>
|
|
645
|
-
<Button variant="ghost" size="sm" className="relative h-9 w-9 p-0">
|
|
646
|
-
<Bell className="h-5 w-5" />
|
|
647
|
-
{notifications.filter(n => !n.read).length > 0 && (
|
|
648
|
-
<span className="absolute -top-1 -right-1 min-w-[1rem] h-4 px-1 rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground flex items-center justify-center">
|
|
649
|
-
{notifications.filter(n => !n.read).length}
|
|
650
|
-
</span>
|
|
651
|
-
)}
|
|
652
|
-
</Button>
|
|
653
|
-
</DropdownMenuTrigger>
|
|
654
|
-
<DropdownMenuContent
|
|
655
|
-
align="end"
|
|
656
|
-
className="w-80"
|
|
657
|
-
sideOffset={8}
|
|
658
|
-
>
|
|
659
|
-
<div className="flex items-center justify-between px-4 py-2">
|
|
660
|
-
<h4 className="text-sm font-semibold">Notifications</h4>
|
|
661
|
-
{notifications.length > 0 && (
|
|
662
|
-
<div className="flex items-center gap-2">
|
|
663
|
-
<Button
|
|
664
|
-
variant="ghost"
|
|
665
|
-
size="sm"
|
|
666
|
-
className="h-auto p-1 text-xs"
|
|
667
|
-
onClick={() => onNotificationMarkAllAsRead?.()}
|
|
668
|
-
>
|
|
669
|
-
<CheckCheck className="h-3 w-3 mr-1" />
|
|
670
|
-
Mark all read
|
|
671
|
-
</Button>
|
|
672
|
-
<Button
|
|
673
|
-
variant="ghost"
|
|
674
|
-
size="sm"
|
|
675
|
-
className="h-auto p-1 text-xs"
|
|
676
|
-
onClick={() => onNotificationClearAll?.()}
|
|
677
|
-
>
|
|
678
|
-
Clear all
|
|
679
|
-
</Button>
|
|
680
|
-
</div>
|
|
681
|
-
)}
|
|
682
|
-
</div>
|
|
683
|
-
<Separator />
|
|
684
|
-
<ScrollArea className="h-[400px]">
|
|
685
|
-
{notifications.length === 0 ? (
|
|
686
|
-
<div className="p-8 text-center text-sm text-muted-foreground">
|
|
687
|
-
<Bell className="h-8 w-8 mx-auto mb-2 opacity-20" />
|
|
688
|
-
<p>No notifications</p>
|
|
689
|
-
</div>
|
|
690
|
-
) : (
|
|
691
|
-
<div className="p-1">
|
|
692
|
-
{notifications.map((notification) => {
|
|
693
|
-
const Icon = notification.type === 'error' ? AlertCircle :
|
|
694
|
-
notification.type === 'warning' ? AlertCircle :
|
|
695
|
-
notification.type === 'success' ? CheckCircle :
|
|
696
|
-
Info;
|
|
697
|
-
|
|
698
|
-
return (
|
|
699
|
-
<motion.div
|
|
700
|
-
key={notification.id}
|
|
701
|
-
initial={{ opacity: 0, x: -20 }}
|
|
702
|
-
animate={{ opacity: 1, x: 0 }}
|
|
703
|
-
className={cn(
|
|
704
|
-
"flex items-start gap-3 p-3 rounded-lg cursor-pointer transition-colors",
|
|
705
|
-
"hover:bg-muted/50",
|
|
706
|
-
!notification.read && "bg-muted/30"
|
|
707
|
-
)}
|
|
708
|
-
onClick={() => {
|
|
709
|
-
onNotificationClick?.(notification);
|
|
710
|
-
if (!notification.read) {
|
|
711
|
-
onNotificationMarkAsRead?.(notification.id);
|
|
712
|
-
}
|
|
713
|
-
}}
|
|
714
|
-
>
|
|
715
|
-
<div className={cn(
|
|
716
|
-
"mt-0.5 p-1.5 rounded-full",
|
|
717
|
-
notification.type === 'error' && "bg-destructive/10 text-destructive",
|
|
718
|
-
notification.type === 'warning' && "bg-yellow-500/10 text-yellow-600 dark:text-yellow-500",
|
|
719
|
-
notification.type === 'success' && "bg-green-500/10 text-green-600 dark:text-green-500",
|
|
720
|
-
notification.type === 'info' && "bg-blue-500/10 text-blue-600 dark:text-blue-500"
|
|
721
|
-
)}>
|
|
722
|
-
<Icon className="h-3.5 w-3.5" />
|
|
723
|
-
</div>
|
|
724
|
-
<div className="flex-1 space-y-1">
|
|
725
|
-
<p className="text-sm font-medium leading-none">
|
|
726
|
-
{notification.title}
|
|
727
|
-
</p>
|
|
728
|
-
{notification.message && (
|
|
729
|
-
<p className="text-xs text-muted-foreground">
|
|
730
|
-
{notification.message}
|
|
731
|
-
</p>
|
|
732
|
-
)}
|
|
733
|
-
<p className="text-xs text-muted-foreground">
|
|
734
|
-
{formatRelativeTime(notification.timestamp)}
|
|
735
|
-
</p>
|
|
736
|
-
</div>
|
|
737
|
-
<Button
|
|
738
|
-
variant="ghost"
|
|
739
|
-
size="sm"
|
|
740
|
-
className="h-6 w-6 p-0"
|
|
741
|
-
onClick={(e) => {
|
|
742
|
-
e.stopPropagation();
|
|
743
|
-
onNotificationClear?.(notification.id);
|
|
744
|
-
}}
|
|
745
|
-
>
|
|
746
|
-
<X className="h-3 w-3" />
|
|
747
|
-
</Button>
|
|
748
|
-
</motion.div>
|
|
749
|
-
);
|
|
750
|
-
})}
|
|
751
|
-
</div>
|
|
752
|
-
)}
|
|
753
|
-
</ScrollArea>
|
|
754
|
-
</DropdownMenuContent>
|
|
755
|
-
</DropdownMenu>
|
|
756
|
-
|
|
757
|
-
{/* Theme selector */}
|
|
758
|
-
<DropdownMenu>
|
|
759
|
-
<DropdownMenuTrigger asChild>
|
|
760
|
-
<Button variant="ghost" size="sm">
|
|
761
|
-
<Palette className="h-4 w-4" />
|
|
762
|
-
</Button>
|
|
763
|
-
</DropdownMenuTrigger>
|
|
764
|
-
<DropdownMenuContent align="end">
|
|
765
|
-
<DropdownMenuItem onClick={() => {
|
|
766
|
-
setSelectedTheme('analytics');
|
|
767
|
-
onThemeChange?.('analytics');
|
|
768
|
-
}}>
|
|
769
|
-
<div className="flex items-center gap-2">
|
|
770
|
-
<div className="h-4 w-4 rounded bg-gradient-to-br from-blue-500 to-purple-500" />
|
|
771
|
-
Analytics
|
|
772
|
-
</div>
|
|
773
|
-
</DropdownMenuItem>
|
|
774
|
-
<DropdownMenuItem onClick={() => {
|
|
775
|
-
setSelectedTheme('sales');
|
|
776
|
-
onThemeChange?.('sales');
|
|
777
|
-
}}>
|
|
778
|
-
<div className="flex items-center gap-2">
|
|
779
|
-
<div className="h-4 w-4 rounded bg-gradient-to-br from-green-500 to-emerald-500" />
|
|
780
|
-
Sales
|
|
781
|
-
</div>
|
|
782
|
-
</DropdownMenuItem>
|
|
783
|
-
<DropdownMenuItem onClick={() => {
|
|
784
|
-
setSelectedTheme('monitoring');
|
|
785
|
-
onThemeChange?.('monitoring');
|
|
786
|
-
}}>
|
|
787
|
-
<div className="flex items-center gap-2">
|
|
788
|
-
<div className="h-4 w-4 rounded bg-gradient-to-br from-orange-500 to-red-500" />
|
|
789
|
-
Monitoring
|
|
790
|
-
</div>
|
|
791
|
-
</DropdownMenuItem>
|
|
792
|
-
</DropdownMenuContent>
|
|
793
|
-
</DropdownMenu>
|
|
794
|
-
|
|
795
|
-
{/* Custom header actions */}
|
|
796
|
-
{headerActions}
|
|
797
|
-
|
|
798
|
-
{/* User Profile */}
|
|
799
|
-
{user && (
|
|
800
|
-
<DropdownMenu>
|
|
801
|
-
<DropdownMenuTrigger asChild>
|
|
802
|
-
<Button variant="ghost" size="sm" className="relative h-8 w-8 rounded-full">
|
|
803
|
-
<Avatar className="h-8 w-8">
|
|
804
|
-
<AvatarImage src={user.avatar} alt={user.name} />
|
|
805
|
-
<AvatarFallback>
|
|
806
|
-
{user.name.split(' ').map(n => n[0]).join('').toUpperCase()}
|
|
807
|
-
</AvatarFallback>
|
|
808
|
-
</Avatar>
|
|
809
|
-
</Button>
|
|
810
|
-
</DropdownMenuTrigger>
|
|
811
|
-
<DropdownMenuContent align="end" className="w-56">
|
|
812
|
-
<div className="flex items-center justify-start gap-2 p-2">
|
|
813
|
-
<div className="flex flex-col space-y-1 leading-none">
|
|
814
|
-
{user.name && (
|
|
815
|
-
<p className="font-medium">{user.name}</p>
|
|
816
|
-
)}
|
|
817
|
-
{user.email && (
|
|
818
|
-
<p className="text-xs text-muted-foreground">
|
|
819
|
-
{user.email}
|
|
820
|
-
</p>
|
|
821
|
-
)}
|
|
822
|
-
</div>
|
|
823
|
-
</div>
|
|
824
|
-
<DropdownMenuSeparator />
|
|
825
|
-
{userMenuItems ? (
|
|
826
|
-
// Custom menu items
|
|
827
|
-
userMenuItems.map((item, index) => (
|
|
828
|
-
item.separator ? (
|
|
829
|
-
<DropdownMenuSeparator key={item.id || `sep-${index}`} />
|
|
830
|
-
) : (
|
|
831
|
-
<DropdownMenuItem
|
|
832
|
-
key={item.id}
|
|
833
|
-
onClick={() => item.onClick?.()}
|
|
834
|
-
>
|
|
835
|
-
{item.icon && <span className="mr-2">{item.icon}</span>}
|
|
836
|
-
{item.label}
|
|
837
|
-
</DropdownMenuItem>
|
|
838
|
-
)
|
|
839
|
-
))
|
|
840
|
-
) : (
|
|
841
|
-
// Default menu items
|
|
842
|
-
<>
|
|
843
|
-
<DropdownMenuItem onClick={() => onProfileClick?.()}>
|
|
844
|
-
<User className="mr-2 h-4 w-4" />
|
|
845
|
-
Profile
|
|
846
|
-
</DropdownMenuItem>
|
|
847
|
-
<DropdownMenuItem onClick={() => onUserMenuClick?.()}>
|
|
848
|
-
<Settings2 className="mr-2 h-4 w-4" />
|
|
849
|
-
Settings
|
|
850
|
-
</DropdownMenuItem>
|
|
851
|
-
<DropdownMenuSeparator />
|
|
852
|
-
<DropdownMenuItem onClick={() => onLogout?.()}>
|
|
853
|
-
<LogOut className="mr-2 h-4 w-4" />
|
|
854
|
-
Log out
|
|
855
|
-
</DropdownMenuItem>
|
|
856
|
-
</>
|
|
857
|
-
)}
|
|
858
|
-
</DropdownMenuContent>
|
|
859
|
-
</DropdownMenu>
|
|
860
|
-
)}
|
|
861
|
-
|
|
862
|
-
{/* More actions */}
|
|
863
|
-
<DropdownMenu>
|
|
864
|
-
<DropdownMenuTrigger asChild>
|
|
865
|
-
<Button variant="outline" size="sm">
|
|
866
|
-
<Settings className="h-4 w-4 mr-2" />
|
|
867
|
-
Actions
|
|
868
|
-
</Button>
|
|
869
|
-
</DropdownMenuTrigger>
|
|
870
|
-
<DropdownMenuContent align="end" className="w-48">
|
|
871
|
-
{editable && (
|
|
872
|
-
<>
|
|
873
|
-
<DropdownMenuItem onClick={() => setEditMode(!editMode)}>
|
|
874
|
-
<Edit3 className="mr-2 h-4 w-4" />
|
|
875
|
-
{editMode ? 'Exit Edit Mode' : 'Edit Layout'}
|
|
876
|
-
</DropdownMenuItem>
|
|
877
|
-
<DropdownMenuItem onClick={() => setShowWidgetLibrary(true)}>
|
|
878
|
-
<Plus className="mr-2 h-4 w-4" />
|
|
879
|
-
Add Widget
|
|
880
|
-
</DropdownMenuItem>
|
|
881
|
-
<DropdownMenuItem onClick={() => setShowTemplates(true)}>
|
|
882
|
-
<LayoutGrid className="mr-2 h-4 w-4" />
|
|
883
|
-
Templates
|
|
884
|
-
</DropdownMenuItem>
|
|
885
|
-
<DropdownMenuSeparator />
|
|
886
|
-
</>
|
|
887
|
-
)}
|
|
888
|
-
|
|
889
|
-
<DropdownMenuSub>
|
|
890
|
-
<DropdownMenuSubTrigger>
|
|
891
|
-
<Download className="mr-2 h-4 w-4" />
|
|
892
|
-
Export
|
|
893
|
-
</DropdownMenuSubTrigger>
|
|
894
|
-
<DropdownMenuSubContent>
|
|
895
|
-
<DropdownMenuItem onClick={() => onExport?.('json')}>
|
|
896
|
-
As JSON
|
|
897
|
-
</DropdownMenuItem>
|
|
898
|
-
<DropdownMenuItem onClick={() => onExport?.('pdf')}>
|
|
899
|
-
As PDF
|
|
900
|
-
</DropdownMenuItem>
|
|
901
|
-
<DropdownMenuItem onClick={() => onExport?.('png')}>
|
|
902
|
-
As Image
|
|
903
|
-
</DropdownMenuItem>
|
|
904
|
-
</DropdownMenuSubContent>
|
|
905
|
-
</DropdownMenuSub>
|
|
906
|
-
|
|
907
|
-
<DropdownMenuItem>
|
|
908
|
-
<Upload className="mr-2 h-4 w-4" />
|
|
909
|
-
Import
|
|
910
|
-
</DropdownMenuItem>
|
|
911
|
-
|
|
912
|
-
<DropdownMenuSeparator />
|
|
913
|
-
|
|
914
|
-
<DropdownMenuItem onClick={() => {
|
|
915
|
-
setRefreshing(true);
|
|
916
|
-
onRefresh?.();
|
|
917
|
-
setTimeout(() => setRefreshing(false), 1000);
|
|
918
|
-
}}>
|
|
919
|
-
<RefreshCw className="mr-2 h-4 w-4" />
|
|
920
|
-
Refresh Data
|
|
921
|
-
</DropdownMenuItem>
|
|
922
|
-
|
|
923
|
-
<DropdownMenuItem>
|
|
924
|
-
<Share2 className="mr-2 h-4 w-4" />
|
|
925
|
-
Share Dashboard
|
|
926
|
-
</DropdownMenuItem>
|
|
927
|
-
</DropdownMenuContent>
|
|
928
|
-
</DropdownMenu>
|
|
929
|
-
</div>
|
|
930
|
-
</div>
|
|
931
|
-
</div>
|
|
932
|
-
|
|
933
|
-
{/* Edit mode banner */}
|
|
934
|
-
<AnimatePresence>
|
|
935
|
-
{editMode && (
|
|
936
|
-
<motion.div
|
|
937
|
-
initial={{ height: 0, opacity: 0 }}
|
|
938
|
-
animate={{ height: "auto", opacity: 1 }}
|
|
939
|
-
exit={{ height: 0, opacity: 0 }}
|
|
940
|
-
className="bg-primary/10 border-t border-primary/20"
|
|
941
|
-
>
|
|
942
|
-
<div className="px-6 py-2 flex items-center justify-between">
|
|
943
|
-
<div className="flex items-center gap-2 text-sm">
|
|
944
|
-
<Sparkles className="h-4 w-4 text-primary" />
|
|
945
|
-
<span>Edit mode enabled - Drag widgets to rearrange</span>
|
|
946
|
-
</div>
|
|
947
|
-
<div className="flex items-center gap-2">
|
|
948
|
-
<Button
|
|
949
|
-
variant="ghost"
|
|
950
|
-
size="sm"
|
|
951
|
-
onClick={() => setEditMode(false)}
|
|
952
|
-
>
|
|
953
|
-
<X className="h-4 w-4 mr-1" />
|
|
954
|
-
Cancel
|
|
955
|
-
</Button>
|
|
956
|
-
<Button
|
|
957
|
-
variant="primary"
|
|
958
|
-
size="sm"
|
|
959
|
-
onClick={() => {
|
|
960
|
-
setEditMode(false)
|
|
961
|
-
// Save layout changes
|
|
962
|
-
}}
|
|
963
|
-
>
|
|
964
|
-
<Save className="h-4 w-4 mr-1" />
|
|
965
|
-
Save Changes
|
|
966
|
-
</Button>
|
|
967
|
-
</div>
|
|
968
|
-
</div>
|
|
969
|
-
</motion.div>
|
|
970
|
-
)}
|
|
971
|
-
</AnimatePresence>
|
|
972
|
-
</motion.div>
|
|
973
|
-
)}
|
|
974
|
-
|
|
975
|
-
{/* Main content */}
|
|
976
|
-
<div className="p-6">
|
|
977
|
-
{widgets.length === 0 ? (
|
|
978
|
-
// Empty state
|
|
979
|
-
<motion.div
|
|
980
|
-
initial={{ opacity: 0, scale: 0.9 }}
|
|
981
|
-
animate={{ opacity: 1, scale: 1 }}
|
|
982
|
-
className="flex flex-col items-center justify-center min-h-[400px] text-center"
|
|
983
|
-
>
|
|
984
|
-
<div className={cn(
|
|
985
|
-
"p-4 rounded-full mb-4",
|
|
986
|
-
glassmorphism ? "bg-background/60 backdrop-blur-sm" : "bg-muted"
|
|
987
|
-
)}>
|
|
988
|
-
<LayoutGrid className="h-8 w-8 text-muted-foreground" />
|
|
989
|
-
</div>
|
|
990
|
-
<h3 className="text-lg font-semibold mb-2">No widgets added yet</h3>
|
|
991
|
-
<p className="text-muted-foreground mb-4">
|
|
992
|
-
Start by adding widgets or choosing a template
|
|
993
|
-
</p>
|
|
994
|
-
<div className="flex gap-2">
|
|
995
|
-
<Button onClick={() => setShowWidgetLibrary(true)}>
|
|
996
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
997
|
-
Add Widget
|
|
998
|
-
</Button>
|
|
999
|
-
<Button variant="outline" onClick={() => setShowTemplates(true)}>
|
|
1000
|
-
<LayoutGrid className="h-4 w-4 mr-2" />
|
|
1001
|
-
Browse Templates
|
|
1002
|
-
</Button>
|
|
1003
|
-
</div>
|
|
1004
|
-
</motion.div>
|
|
1005
|
-
) : (
|
|
1006
|
-
// Dashboard grid
|
|
1007
|
-
<DashboardGrid
|
|
1008
|
-
widgets={widgets}
|
|
1009
|
-
onLayoutChange={(layout) => {
|
|
1010
|
-
// Update widget positions
|
|
1011
|
-
const updatedWidgets = widgets.map(widget => {
|
|
1012
|
-
const layoutItem = layout.find(l => l.i === widget.id)
|
|
1013
|
-
if (layoutItem) {
|
|
1014
|
-
return {
|
|
1015
|
-
...widget,
|
|
1016
|
-
position: { x: layoutItem.x, y: layoutItem.y },
|
|
1017
|
-
size: { ...widget.size, w: layoutItem.w, h: layoutItem.h }
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
return widget
|
|
1021
|
-
})
|
|
1022
|
-
setWidgets(updatedWidgets)
|
|
1023
|
-
}}
|
|
1024
|
-
onWidgetRemove={(widgetId) => {
|
|
1025
|
-
setWidgets(prev => prev.filter(w => w.id !== widgetId))
|
|
1026
|
-
onWidgetRemove?.(widgetId)
|
|
1027
|
-
}}
|
|
1028
|
-
onWidgetAction={(widgetId, action, data) => {
|
|
1029
|
-
console.log('Widget action:', widgetId, action, data)
|
|
1030
|
-
// Handle widget actions
|
|
1031
|
-
}}
|
|
1032
|
-
editMode={editMode}
|
|
1033
|
-
glassmorphism={glassmorphism}
|
|
1034
|
-
/>
|
|
1035
|
-
)}
|
|
1036
|
-
</div>
|
|
1037
|
-
|
|
1038
|
-
{/* Modals */}
|
|
1039
|
-
<WidgetLibrary />
|
|
1040
|
-
<TemplateGallery />
|
|
1041
|
-
</motion.div>
|
|
1042
|
-
)
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
export type { Widget, MetricData, ChartData, ActivityItem, ProgressData, ComparisonData } from './types'
|
|
1046
|
-
export default Dashboard
|