@moontra/moonui-pro 2.4.5 → 2.5.0

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.
@@ -1,333 +1,741 @@
1
1
  "use client"
2
2
 
3
3
  import React from 'react'
4
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'
5
- import { MoonUIBadgePro as Badge } from '../ui/badge'
4
+ import { motion, AnimatePresence } from 'framer-motion'
6
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'
7
10
  import {
8
- TrendingUp,
9
- TrendingDown,
10
- Users,
11
- DollarSign,
12
- Activity,
13
- BarChart3,
14
- Eye,
11
+ Activity,
15
12
  Download,
16
- Star,
17
- Clock,
13
+ Upload,
14
+ Settings,
15
+ Edit3,
16
+ Save,
17
+ X,
18
+ Plus,
19
+ RefreshCw,
20
+ Share2,
21
+ Filter,
22
+ LayoutGrid,
23
+ Palette,
24
+ Search,
25
+ Bell,
26
+ Menu,
27
+ ChevronRight,
28
+ Sparkles,
29
+ BarChart3,
30
+ Users,
31
+ DollarSign,
32
+ TrendingUp,
18
33
  Calendar,
34
+ Map,
35
+ Table,
36
+ Clock,
37
+ Target,
19
38
  ArrowUpRight,
20
- ArrowDownRight,
21
- Minus,
22
- Lock,
23
- Sparkles
39
+ CheckCircle
24
40
  } from 'lucide-react'
25
41
  import { cn } from '../../lib/utils'
26
-
27
- interface DashboardMetric {
28
- id: string
29
- title: string
30
- value: string | number
31
- change?: {
32
- value: number
33
- type: 'increase' | 'decrease' | 'neutral'
34
- period: string
35
- }
36
- icon?: React.ReactNode
37
- color?: string
38
- description?: string
39
- trend?: number[]
40
- }
41
-
42
- interface DashboardWidget {
43
- id: string
44
- title: string
45
- description?: string
46
- content: React.ReactNode
47
- size?: 'sm' | 'md' | 'lg' | 'xl'
48
- loading?: boolean
49
- error?: string
50
- }
42
+ import { DashboardGrid } from './dashboard-grid'
43
+ import { TimeRangePicker } from './time-range-picker'
44
+ import { MetricCard } from './widgets/metric-card'
45
+ import { ChartWidget } from './widgets/chart-widget'
46
+ import { ActivityFeed } from './widgets/activity-feed'
47
+ import {
48
+ Widget,
49
+ DashboardConfig,
50
+ DashboardTheme,
51
+ TimeRange,
52
+ MetricData,
53
+ ChartData,
54
+ ActivityItem,
55
+ DashboardTemplate,
56
+ WidgetType
57
+ } from './types'
58
+ import {
59
+ DropdownMenu,
60
+ DropdownMenuContent,
61
+ DropdownMenuItem,
62
+ DropdownMenuSeparator,
63
+ DropdownMenuSub,
64
+ DropdownMenuSubContent,
65
+ DropdownMenuSubTrigger,
66
+ DropdownMenuTrigger,
67
+ } from '../ui/dropdown-menu'
68
+ import {
69
+ Dialog,
70
+ DialogContent,
71
+ DialogDescription,
72
+ DialogHeader,
73
+ DialogTitle,
74
+ DialogTrigger,
75
+ } from '../ui/dialog'
76
+ import {
77
+ Sheet,
78
+ SheetContent,
79
+ SheetDescription,
80
+ SheetHeader,
81
+ SheetTitle,
82
+ SheetTrigger,
83
+ } from '../ui/sheet'
51
84
 
52
85
  interface DashboardProps {
53
- metrics?: DashboardMetric[]
54
- widgets?: DashboardWidget[]
55
- onMetricClick?: (metric: DashboardMetric) => void
56
- onWidgetAction?: (widgetId: string, action: string) => void
86
+ config?: DashboardConfig
87
+ widgets?: Widget[]
88
+ templates?: DashboardTemplate[]
89
+ onConfigChange?: (config: DashboardConfig) => void
90
+ onWidgetAdd?: (widget: Omit<Widget, 'id'>) => void
91
+ onWidgetRemove?: (widgetId: string) => void
92
+ onWidgetUpdate?: (widgetId: string, updates: Partial<Widget>) => void
93
+ onExport?: (format: 'json' | 'pdf' | 'png') => void
94
+ onImport?: (file: File) => void
57
95
  className?: string
58
96
  showHeader?: boolean
59
97
  title?: string
60
98
  description?: string
61
- refreshable?: boolean
62
- onRefresh?: () => void
63
- loading?: boolean
99
+ editable?: boolean
100
+ realtime?: boolean
101
+ glassmorphism?: boolean
64
102
  }
65
103
 
66
- const METRIC_COLORS = {
67
- primary: 'text-blue-600',
68
- success: 'text-green-600',
69
- warning: 'text-yellow-600',
70
- danger: 'text-red-600',
71
- info: 'text-purple-600'
104
+ // Dashboard template'leri
105
+ const DASHBOARD_TEMPLATES: DashboardTemplate[] = [
106
+ {
107
+ id: 'analytics',
108
+ name: 'Analytics Dashboard',
109
+ description: 'Comprehensive analytics with metrics and charts',
110
+ category: 'analytics',
111
+ theme: 'analytics',
112
+ widgets: [
113
+ {
114
+ type: 'metric',
115
+ title: 'Total Revenue',
116
+ size: { w: 3, h: 2 },
117
+ position: { x: 0, y: 0 },
118
+ data: {
119
+ id: 'revenue',
120
+ title: 'Total Revenue',
121
+ value: 125430,
122
+ change: { value: 12, type: 'increase', period: 'last month' },
123
+ color: 'success',
124
+ icon: <DollarSign className="h-4 w-4" />,
125
+ sparkline: [100, 120, 115, 125, 130, 128, 132],
126
+ unit: '$'
127
+ }
128
+ },
129
+ {
130
+ type: 'metric',
131
+ title: 'Active Users',
132
+ size: { w: 3, h: 2 },
133
+ position: { x: 3, y: 0 },
134
+ data: {
135
+ id: 'users',
136
+ title: 'Active Users',
137
+ value: 2543,
138
+ change: { value: 8, type: 'increase', period: 'last week' },
139
+ color: 'primary',
140
+ icon: <Users className="h-4 w-4" />,
141
+ sparkline: [200, 220, 210, 230, 225, 240, 254]
142
+ }
143
+ },
144
+ {
145
+ type: 'chart',
146
+ title: 'Revenue Trend',
147
+ size: { w: 6, h: 4 },
148
+ position: { x: 0, y: 2 },
149
+ data: {
150
+ type: 'area',
151
+ data: [
152
+ { name: 'Jan', revenue: 4000, profit: 2400 },
153
+ { name: 'Feb', revenue: 3000, profit: 1398 },
154
+ { name: 'Mar', revenue: 5000, profit: 3200 },
155
+ { name: 'Apr', revenue: 4500, profit: 2900 },
156
+ { name: 'May', revenue: 6000, profit: 3800 },
157
+ { name: 'Jun', revenue: 5500, profit: 3400 }
158
+ ]
159
+ }
160
+ },
161
+ {
162
+ type: 'activity',
163
+ title: 'Recent Activity',
164
+ size: { w: 3, h: 6 },
165
+ position: { x: 9, y: 0 },
166
+ data: {
167
+ realtime: true,
168
+ items: [
169
+ {
170
+ id: '1',
171
+ type: 'success',
172
+ title: 'completed a purchase',
173
+ description: 'Order #12345 - $250.00',
174
+ timestamp: new Date(Date.now() - 1000 * 60 * 5),
175
+ user: { name: 'John Doe' }
176
+ },
177
+ {
178
+ id: '2',
179
+ type: 'info',
180
+ title: 'signed up',
181
+ description: 'New user registration',
182
+ timestamp: new Date(Date.now() - 1000 * 60 * 15),
183
+ user: { name: 'Jane Smith' }
184
+ }
185
+ ]
186
+ }
187
+ }
188
+ ]
189
+ },
190
+ {
191
+ id: 'sales',
192
+ name: 'Sales Dashboard',
193
+ description: 'Track sales performance and targets',
194
+ category: 'sales',
195
+ theme: 'sales',
196
+ widgets: [
197
+ // Sales-specific widgets
198
+ ]
199
+ },
200
+ {
201
+ id: 'monitoring',
202
+ name: 'System Monitoring',
203
+ description: 'Monitor system health and performance',
204
+ category: 'operations',
205
+ theme: 'monitoring',
206
+ widgets: [
207
+ // Monitoring widgets
208
+ ]
209
+ }
210
+ ]
211
+
212
+ // Widget türleri için ikonlar
213
+ const WIDGET_TYPE_ICONS: Record<WidgetType, React.ReactNode> = {
214
+ metric: <BarChart3 className="h-4 w-4" />,
215
+ chart: <Activity className="h-4 w-4" />,
216
+ table: <Table className="h-4 w-4" />,
217
+ map: <Map className="h-4 w-4" />,
218
+ activity: <Clock className="h-4 w-4" />,
219
+ calendar: <Calendar className="h-4 w-4" />,
220
+ progress: <Target className="h-4 w-4" />,
221
+ comparison: <TrendingUp className="h-4 w-4" />
72
222
  }
73
223
 
74
- const WIDGET_SIZES = {
75
- sm: 'col-span-1',
76
- md: 'col-span-2',
77
- lg: 'col-span-3',
78
- xl: 'col-span-4'
224
+ // Tema renkleri
225
+ const THEME_COLORS: Record<DashboardTheme, string> = {
226
+ analytics: 'from-blue-500/10 to-purple-500/10',
227
+ sales: 'from-green-500/10 to-emerald-500/10',
228
+ monitoring: 'from-orange-500/10 to-red-500/10',
229
+ finance: 'from-indigo-500/10 to-blue-500/10',
230
+ custom: 'from-gray-500/10 to-gray-600/10'
79
231
  }
80
232
 
81
233
  export function Dashboard({
82
- metrics = [],
83
- widgets = [],
84
- onMetricClick,
85
- onWidgetAction,
234
+ config,
235
+ widgets: initialWidgets = [],
236
+ templates = DASHBOARD_TEMPLATES,
237
+ onConfigChange,
238
+ onWidgetAdd,
239
+ onWidgetRemove,
240
+ onWidgetUpdate,
241
+ onExport,
242
+ onImport,
86
243
  className,
87
244
  showHeader = true,
88
245
  title = 'Dashboard',
89
- description = 'Overview of your key metrics and performance',
90
- refreshable = true,
91
- onRefresh,
92
- loading = false
246
+ description = 'Real-time analytics and insights',
247
+ editable = true,
248
+ realtime = false,
249
+ glassmorphism = true
93
250
  }: DashboardProps) {
251
+ // State yönetimi
252
+ const [editMode, setEditMode] = React.useState(false)
253
+ const [widgets, setWidgets] = React.useState<Widget[]>(initialWidgets)
254
+ const [selectedTheme, setSelectedTheme] = React.useState<DashboardTheme>('analytics')
255
+ const [timeRange, setTimeRange] = React.useState<TimeRange>()
256
+ const [searchQuery, setSearchQuery] = React.useState('')
257
+ const [showWidgetLibrary, setShowWidgetLibrary] = React.useState(false)
258
+ const [showTemplates, setShowTemplates] = React.useState(false)
94
259
  const [refreshing, setRefreshing] = React.useState(false)
260
+ const [notifications, setNotifications] = React.useState<number>(3)
95
261
 
96
- const handleRefresh = async () => {
97
- if (onRefresh) {
98
- setRefreshing(true)
99
- try {
100
- await onRefresh()
101
- } finally {
102
- setRefreshing(false)
103
- }
104
- }
105
- }
262
+ // WebSocket bağlantısı (real-time için)
263
+ React.useEffect(() => {
264
+ if (!realtime) return
106
265
 
107
- const formatValue = (value: string | number): string => {
108
- if (typeof value === 'number') {
109
- if (value >= 1000000) {
110
- return (value / 1000000).toFixed(1) + 'M'
111
- } else if (value >= 1000) {
112
- return (value / 1000).toFixed(1) + 'K'
113
- }
114
- return value.toLocaleString()
266
+ // Simüle edilmiş WebSocket bağlantısı
267
+ const interval = setInterval(() => {
268
+ // Widget'ları güncelle
269
+ setWidgets(prev => prev.map(widget => {
270
+ if (widget.type === 'metric' && widget.data) {
271
+ const newValue = typeof widget.data.value === 'number'
272
+ ? widget.data.value + Math.floor(Math.random() * 10 - 5)
273
+ : widget.data.value
274
+
275
+ return {
276
+ ...widget,
277
+ data: {
278
+ ...widget.data,
279
+ value: newValue,
280
+ lastUpdated: new Date()
281
+ }
282
+ }
283
+ }
284
+ return widget
285
+ }))
286
+ }, 5000)
287
+
288
+ return () => clearInterval(interval)
289
+ }, [realtime])
290
+
291
+ // Widget ekle
292
+ const handleAddWidget = (type: WidgetType) => {
293
+ const newWidget: Widget = {
294
+ id: `widget-${Date.now()}`,
295
+ type,
296
+ title: `New ${type} Widget`,
297
+ size: { w: 3, h: 3 },
298
+ position: { x: 0, y: 0 },
299
+ data: generateDefaultData(type)
115
300
  }
116
- return value.toString()
301
+
302
+ setWidgets(prev => [...prev, newWidget])
303
+ onWidgetAdd?.(newWidget)
304
+ setShowWidgetLibrary(false)
117
305
  }
118
306
 
119
- const getChangeIcon = (type: 'increase' | 'decrease' | 'neutral') => {
120
- switch (type) {
121
- case 'increase':
122
- return <ArrowUpRight className="h-4 w-4" />
123
- case 'decrease':
124
- return <ArrowDownRight className="h-4 w-4" />
125
- case 'neutral':
126
- return <Minus className="h-4 w-4" />
127
- }
307
+ // Template uygula
308
+ const applyTemplate = (template: DashboardTemplate) => {
309
+ const newWidgets = template.widgets.map((w, index) => ({
310
+ ...w,
311
+ id: `widget-${Date.now()}-${index}`
312
+ }))
313
+
314
+ setWidgets(newWidgets as Widget[])
315
+ setSelectedTheme(template.theme)
316
+ setShowTemplates(false)
128
317
  }
129
318
 
130
- const getChangeColor = (type: 'increase' | 'decrease' | 'neutral') => {
319
+ // Varsayılan widget verisi oluştur
320
+ const generateDefaultData = (type: WidgetType): any => {
131
321
  switch (type) {
132
- case 'increase':
133
- return 'text-green-600'
134
- case 'decrease':
135
- return 'text-red-600'
136
- case 'neutral':
137
- return 'text-gray-600'
322
+ case 'metric':
323
+ return {
324
+ id: 'new-metric',
325
+ title: 'New Metric',
326
+ value: 0,
327
+ icon: <BarChart3 className="h-4 w-4" />,
328
+ color: 'primary'
329
+ }
330
+ case 'chart':
331
+ return {
332
+ type: 'line',
333
+ data: [
334
+ { name: 'Mon', value: 100 },
335
+ { name: 'Tue', value: 120 },
336
+ { name: 'Wed', value: 110 },
337
+ { name: 'Thu', value: 130 },
338
+ { name: 'Fri', value: 125 }
339
+ ]
340
+ }
341
+ case 'activity':
342
+ return {
343
+ items: [],
344
+ realtime: true
345
+ }
346
+ default:
347
+ return {}
138
348
  }
139
349
  }
140
350
 
141
- const renderMetricCard = (metric: DashboardMetric) => {
142
- const colorClass = metric.color ? METRIC_COLORS[metric.color as keyof typeof METRIC_COLORS] : 'text-foreground'
143
-
144
- return (
145
- <Card
146
- key={metric.id}
147
- className={cn(
148
- "cursor-pointer hover:shadow-md transition-shadow",
149
- onMetricClick && "hover:bg-muted/50"
150
- )}
151
- onClick={() => onMetricClick?.(metric)}
152
- >
153
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
154
- <CardTitle className="text-sm font-medium">
155
- {metric.title}
156
- </CardTitle>
157
- <div className={cn("h-4 w-4", colorClass)}>
158
- {metric.icon}
351
+ // Widget kütüphanesi
352
+ const WidgetLibrary = () => (
353
+ <Sheet open={showWidgetLibrary} onOpenChange={setShowWidgetLibrary}>
354
+ <SheetContent className={cn(
355
+ "w-[400px] sm:w-[540px]",
356
+ glassmorphism && "bg-background/95 backdrop-blur-md"
357
+ )}>
358
+ <SheetHeader>
359
+ <SheetTitle>Widget Library</SheetTitle>
360
+ <SheetDescription>
361
+ Add widgets to customize your dashboard
362
+ </SheetDescription>
363
+ </SheetHeader>
364
+
365
+ <div className="mt-6 space-y-4">
366
+ <div className="relative">
367
+ <Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
368
+ <Input
369
+ placeholder="Search widgets..."
370
+ value={searchQuery}
371
+ onChange={(e) => setSearchQuery(e.target.value)}
372
+ className="pl-10"
373
+ />
159
374
  </div>
160
- </CardHeader>
161
- <CardContent>
162
- <div className="text-2xl font-bold">{formatValue(metric.value)}</div>
163
- {metric.change && (
164
- <div className={cn(
165
- "flex items-center text-xs mt-1",
166
- getChangeColor(metric.change.type)
167
- )}>
168
- {getChangeIcon(metric.change.type)}
169
- <span className="ml-1">
170
- {Math.abs(metric.change.value)}% from {metric.change.period}
171
- </span>
172
- </div>
173
- )}
174
- {metric.description && (
175
- <p className="text-xs text-muted-foreground mt-1">
176
- {metric.description}
177
- </p>
178
- )}
179
- </CardContent>
180
- </Card>
181
- )
182
- }
183
375
 
184
- const renderWidget = (widget: DashboardWidget) => {
185
- const sizeClass = WIDGET_SIZES[widget.size || 'md']
186
-
187
- return (
188
- <Card key={widget.id} className={cn("h-fit", sizeClass)}>
189
- <CardHeader>
190
- <div className="flex items-center justify-between">
191
- <div>
192
- <CardTitle className="text-base">{widget.title}</CardTitle>
193
- {widget.description && (
194
- <CardDescription className="mt-1">
195
- {widget.description}
196
- </CardDescription>
197
- )}
198
- </div>
199
- <Button
200
- variant="ghost"
201
- size="sm"
202
- onClick={() => onWidgetAction?.(widget.id, 'menu')}
203
- >
204
- <BarChart3 className="h-4 w-4" />
205
- </Button>
206
- </div>
207
- </CardHeader>
208
- <CardContent>
209
- {widget.loading ? (
210
- <div className="flex items-center justify-center h-32">
211
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
376
+ <ScrollArea className="h-[calc(100vh-200px)]">
377
+ <div className="grid grid-cols-2 gap-4">
378
+ {Object.entries(WIDGET_TYPE_ICONS)
379
+ .filter(([type]) => type.toLowerCase().includes(searchQuery.toLowerCase()))
380
+ .map(([type, icon]) => (
381
+ <motion.div
382
+ key={type}
383
+ whileHover={{ scale: 1.02 }}
384
+ whileTap={{ scale: 0.98 }}
385
+ >
386
+ <Button
387
+ variant="outline"
388
+ className="w-full h-24 flex-col gap-2"
389
+ onClick={() => handleAddWidget(type as WidgetType)}
390
+ >
391
+ <div className="p-2 rounded-lg bg-muted">
392
+ {icon}
393
+ </div>
394
+ <span className="capitalize text-sm">{type}</span>
395
+ </Button>
396
+ </motion.div>
397
+ ))}
212
398
  </div>
213
- ) : widget.error ? (
214
- <div className="flex items-center justify-center h-32 text-destructive">
215
- <p className="text-sm">{widget.error}</p>
216
- </div>
217
- ) : (
218
- widget.content
219
- )}
220
- </CardContent>
221
- </Card>
222
- )
223
- }
399
+ </ScrollArea>
400
+ </div>
401
+ </SheetContent>
402
+ </Sheet>
403
+ )
224
404
 
225
- const defaultMetrics: DashboardMetric[] = [
226
- {
227
- id: 'total-users',
228
- title: 'Total Users',
229
- value: 2543,
230
- change: { value: 12, type: 'increase', period: 'last month' },
231
- icon: <Users className="h-4 w-4" />,
232
- color: 'primary'
233
- },
234
- {
235
- id: 'revenue',
236
- title: 'Revenue',
237
- value: '$12,345',
238
- change: { value: 8, type: 'increase', period: 'last month' },
239
- icon: <DollarSign className="h-4 w-4" />,
240
- color: 'success'
241
- },
242
- {
243
- id: 'active-sessions',
244
- title: 'Active Sessions',
245
- value: 1234,
246
- change: { value: 2, type: 'decrease', period: 'last hour' },
247
- icon: <Activity className="h-4 w-4" />,
248
- color: 'warning'
249
- },
250
- {
251
- id: 'conversion-rate',
252
- title: 'Conversion Rate',
253
- value: '3.2%',
254
- change: { value: 0.3, type: 'increase', period: 'last week' },
255
- icon: <TrendingUp className="h-4 w-4" />,
256
- color: 'info'
257
- }
258
- ]
405
+ // Template galerisi
406
+ const TemplateGallery = () => (
407
+ <Dialog open={showTemplates} onOpenChange={setShowTemplates}>
408
+ <DialogContent className={cn(
409
+ "max-w-4xl",
410
+ glassmorphism && "bg-background/95 backdrop-blur-md"
411
+ )}>
412
+ <DialogHeader>
413
+ <DialogTitle>Dashboard Templates</DialogTitle>
414
+ <DialogDescription>
415
+ Start with a pre-built template or create your own
416
+ </DialogDescription>
417
+ </DialogHeader>
259
418
 
260
- const displayMetrics = metrics.length > 0 ? metrics : defaultMetrics
419
+ <Tabs defaultValue="all" className="mt-4">
420
+ <TabsList>
421
+ <TabsTrigger value="all">All Templates</TabsTrigger>
422
+ <TabsTrigger value="analytics">Analytics</TabsTrigger>
423
+ <TabsTrigger value="sales">Sales</TabsTrigger>
424
+ <TabsTrigger value="operations">Operations</TabsTrigger>
425
+ </TabsList>
261
426
 
262
- if (loading) {
263
- return (
264
- <div className={cn("w-full", className)}>
265
- <div className="animate-pulse space-y-4">
266
- <div className="h-8 bg-muted rounded w-1/4"></div>
267
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
268
- {[...Array(4)].map((_, i) => (
269
- <div key={i} className="h-32 bg-muted rounded"></div>
270
- ))}
271
- </div>
272
- </div>
273
- </div>
274
- )
275
- }
427
+ <TabsContent value="all" className="mt-4">
428
+ <div className="grid grid-cols-3 gap-4">
429
+ {templates.map((template) => (
430
+ <motion.div
431
+ key={template.id}
432
+ whileHover={{ y: -4 }}
433
+ className="cursor-pointer"
434
+ onClick={() => applyTemplate(template)}
435
+ >
436
+ <div className={cn(
437
+ "relative overflow-hidden rounded-lg border p-4 hover:shadow-lg transition-all",
438
+ "bg-gradient-to-br", THEME_COLORS[template.theme]
439
+ )}>
440
+ {template.isPro && (
441
+ <Badge className="absolute top-2 right-2" variant="secondary">
442
+ PRO
443
+ </Badge>
444
+ )}
445
+ <h3 className="font-semibold mb-1">{template.name}</h3>
446
+ <p className="text-sm text-muted-foreground">
447
+ {template.description}
448
+ </p>
449
+ <div className="mt-3 flex items-center gap-2 text-xs text-muted-foreground">
450
+ <span>{template.widgets.length} widgets</span>
451
+ <span>•</span>
452
+ <span className="capitalize">{template.category}</span>
453
+ </div>
454
+ </div>
455
+ </motion.div>
456
+ ))}
457
+ </div>
458
+ </TabsContent>
459
+ </Tabs>
460
+ </DialogContent>
461
+ </Dialog>
462
+ )
276
463
 
277
464
  return (
278
- <div className={cn("w-full space-y-6", className)}>
465
+ <motion.div
466
+ className={cn(
467
+ "w-full min-h-screen",
468
+ glassmorphism && "bg-gradient-to-br",
469
+ glassmorphism && THEME_COLORS[selectedTheme],
470
+ className
471
+ )}
472
+ initial={{ opacity: 0 }}
473
+ animate={{ opacity: 1 }}
474
+ transition={{ duration: 0.5 }}
475
+ >
279
476
  {/* Header */}
280
477
  {showHeader && (
281
- <div className="flex items-center justify-between">
282
- <div>
283
- <h1 className="text-2xl font-bold tracking-tight">{title}</h1>
284
- <p className="text-muted-foreground">{description}</p>
285
- </div>
286
- {refreshable && (
287
- <Button
288
- variant="outline"
289
- onClick={handleRefresh}
290
- disabled={refreshing}
291
- >
292
- {refreshing ? (
293
- <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary mr-2"></div>
294
- ) : (
295
- <Activity className="h-4 w-4 mr-2" />
296
- )}
297
- Refresh
298
- </Button>
478
+ <motion.div
479
+ className={cn(
480
+ "sticky top-0 z-30 border-b",
481
+ glassmorphism ? "bg-background/80 backdrop-blur-md" : "bg-background"
299
482
  )}
300
- </div>
301
- )}
483
+ initial={{ y: -50 }}
484
+ animate={{ y: 0 }}
485
+ transition={{ type: "spring", stiffness: 300, damping: 30 }}
486
+ >
487
+ <div className="px-6 py-4">
488
+ <div className="flex items-center justify-between">
489
+ <div className="flex items-center gap-4">
490
+ <motion.div
491
+ whileHover={{ scale: 1.05 }}
492
+ whileTap={{ scale: 0.95 }}
493
+ >
494
+ <Button variant="ghost" size="sm" className="lg:hidden">
495
+ <Menu className="h-5 w-5" />
496
+ </Button>
497
+ </motion.div>
498
+
499
+ <div>
500
+ <div className="flex items-center gap-2">
501
+ <h1 className="text-2xl font-bold tracking-tight">{title}</h1>
502
+ {realtime && (
503
+ <Badge variant="secondary" className="animate-pulse">
504
+ <span className="mr-1 h-1.5 w-1.5 rounded-full bg-green-500" />
505
+ Real-time
506
+ </Badge>
507
+ )}
508
+ </div>
509
+ <p className="text-sm text-muted-foreground">{description}</p>
510
+ </div>
511
+ </div>
302
512
 
303
- {/* Metrics Grid */}
304
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
305
- {displayMetrics.map(renderMetricCard)}
306
- </div>
513
+ {/* Header actions */}
514
+ <div className="flex items-center gap-2">
515
+ {/* Time range picker */}
516
+ <TimeRangePicker
517
+ value={timeRange}
518
+ onChange={setTimeRange}
519
+ glassmorphism={glassmorphism}
520
+ />
307
521
 
308
- {/* Widgets Grid */}
309
- {widgets.length > 0 && (
310
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
311
- {widgets.map(renderWidget)}
312
- </div>
522
+ {/* Notifications */}
523
+ <div className="relative">
524
+ <Button variant="ghost" size="sm" className="relative">
525
+ <Bell className="h-4 w-4" />
526
+ {notifications > 0 && (
527
+ <span className="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground flex items-center justify-center">
528
+ {notifications}
529
+ </span>
530
+ )}
531
+ </Button>
532
+ </div>
533
+
534
+ {/* Theme selector */}
535
+ <DropdownMenu>
536
+ <DropdownMenuTrigger asChild>
537
+ <Button variant="ghost" size="sm">
538
+ <Palette className="h-4 w-4" />
539
+ </Button>
540
+ </DropdownMenuTrigger>
541
+ <DropdownMenuContent align="end">
542
+ <DropdownMenuItem onClick={() => setSelectedTheme('analytics')}>
543
+ <div className="flex items-center gap-2">
544
+ <div className="h-4 w-4 rounded bg-gradient-to-br from-blue-500 to-purple-500" />
545
+ Analytics
546
+ </div>
547
+ </DropdownMenuItem>
548
+ <DropdownMenuItem onClick={() => setSelectedTheme('sales')}>
549
+ <div className="flex items-center gap-2">
550
+ <div className="h-4 w-4 rounded bg-gradient-to-br from-green-500 to-emerald-500" />
551
+ Sales
552
+ </div>
553
+ </DropdownMenuItem>
554
+ <DropdownMenuItem onClick={() => setSelectedTheme('monitoring')}>
555
+ <div className="flex items-center gap-2">
556
+ <div className="h-4 w-4 rounded bg-gradient-to-br from-orange-500 to-red-500" />
557
+ Monitoring
558
+ </div>
559
+ </DropdownMenuItem>
560
+ </DropdownMenuContent>
561
+ </DropdownMenu>
562
+
563
+ {/* More actions */}
564
+ <DropdownMenu>
565
+ <DropdownMenuTrigger asChild>
566
+ <Button variant="outline" size="sm">
567
+ <Settings className="h-4 w-4 mr-2" />
568
+ Actions
569
+ </Button>
570
+ </DropdownMenuTrigger>
571
+ <DropdownMenuContent align="end" className="w-48">
572
+ {editable && (
573
+ <>
574
+ <DropdownMenuItem onClick={() => setEditMode(!editMode)}>
575
+ <Edit3 className="mr-2 h-4 w-4" />
576
+ {editMode ? 'Exit Edit Mode' : 'Edit Layout'}
577
+ </DropdownMenuItem>
578
+ <DropdownMenuItem onClick={() => setShowWidgetLibrary(true)}>
579
+ <Plus className="mr-2 h-4 w-4" />
580
+ Add Widget
581
+ </DropdownMenuItem>
582
+ <DropdownMenuItem onClick={() => setShowTemplates(true)}>
583
+ <LayoutGrid className="mr-2 h-4 w-4" />
584
+ Templates
585
+ </DropdownMenuItem>
586
+ <DropdownMenuSeparator />
587
+ </>
588
+ )}
589
+
590
+ <DropdownMenuSub>
591
+ <DropdownMenuSubTrigger>
592
+ <Download className="mr-2 h-4 w-4" />
593
+ Export
594
+ </DropdownMenuSubTrigger>
595
+ <DropdownMenuSubContent>
596
+ <DropdownMenuItem onClick={() => onExport?.('json')}>
597
+ As JSON
598
+ </DropdownMenuItem>
599
+ <DropdownMenuItem onClick={() => onExport?.('pdf')}>
600
+ As PDF
601
+ </DropdownMenuItem>
602
+ <DropdownMenuItem onClick={() => onExport?.('png')}>
603
+ As Image
604
+ </DropdownMenuItem>
605
+ </DropdownMenuSubContent>
606
+ </DropdownMenuSub>
607
+
608
+ <DropdownMenuItem>
609
+ <Upload className="mr-2 h-4 w-4" />
610
+ Import
611
+ </DropdownMenuItem>
612
+
613
+ <DropdownMenuSeparator />
614
+
615
+ <DropdownMenuItem onClick={() => setRefreshing(true)}>
616
+ <RefreshCw className="mr-2 h-4 w-4" />
617
+ Refresh Data
618
+ </DropdownMenuItem>
619
+
620
+ <DropdownMenuItem>
621
+ <Share2 className="mr-2 h-4 w-4" />
622
+ Share Dashboard
623
+ </DropdownMenuItem>
624
+ </DropdownMenuContent>
625
+ </DropdownMenu>
626
+ </div>
627
+ </div>
628
+ </div>
629
+
630
+ {/* Edit mode banner */}
631
+ <AnimatePresence>
632
+ {editMode && (
633
+ <motion.div
634
+ initial={{ height: 0, opacity: 0 }}
635
+ animate={{ height: "auto", opacity: 1 }}
636
+ exit={{ height: 0, opacity: 0 }}
637
+ className="bg-primary/10 border-t border-primary/20"
638
+ >
639
+ <div className="px-6 py-2 flex items-center justify-between">
640
+ <div className="flex items-center gap-2 text-sm">
641
+ <Sparkles className="h-4 w-4 text-primary" />
642
+ <span>Edit mode enabled - Drag widgets to rearrange</span>
643
+ </div>
644
+ <div className="flex items-center gap-2">
645
+ <Button
646
+ variant="ghost"
647
+ size="sm"
648
+ onClick={() => setEditMode(false)}
649
+ >
650
+ <X className="h-4 w-4 mr-1" />
651
+ Cancel
652
+ </Button>
653
+ <Button
654
+ variant="primary"
655
+ size="sm"
656
+ onClick={() => {
657
+ setEditMode(false)
658
+ // Save layout changes
659
+ }}
660
+ >
661
+ <Save className="h-4 w-4 mr-1" />
662
+ Save Changes
663
+ </Button>
664
+ </div>
665
+ </div>
666
+ </motion.div>
667
+ )}
668
+ </AnimatePresence>
669
+ </motion.div>
313
670
  )}
314
671
 
315
- {/* Quick Actions */}
316
- <div className="flex items-center gap-2 pt-4 border-t">
317
- <Button variant="outline" size="sm">
318
- <Download className="h-4 w-4 mr-2" />
319
- Export Data
320
- </Button>
321
- <Button variant="outline" size="sm">
322
- <Calendar className="h-4 w-4 mr-2" />
323
- Schedule Report
324
- </Button>
325
- <Button variant="outline" size="sm">
326
- <Star className="h-4 w-4 mr-2" />
327
- Save View
328
- </Button>
672
+ {/* Main content */}
673
+ <div className="p-6">
674
+ {widgets.length === 0 ? (
675
+ // Empty state
676
+ <motion.div
677
+ initial={{ opacity: 0, scale: 0.9 }}
678
+ animate={{ opacity: 1, scale: 1 }}
679
+ className="flex flex-col items-center justify-center min-h-[400px] text-center"
680
+ >
681
+ <div className={cn(
682
+ "p-4 rounded-full mb-4",
683
+ glassmorphism ? "bg-background/60 backdrop-blur-sm" : "bg-muted"
684
+ )}>
685
+ <LayoutGrid className="h-8 w-8 text-muted-foreground" />
686
+ </div>
687
+ <h3 className="text-lg font-semibold mb-2">No widgets added yet</h3>
688
+ <p className="text-muted-foreground mb-4">
689
+ Start by adding widgets or choosing a template
690
+ </p>
691
+ <div className="flex gap-2">
692
+ <Button onClick={() => setShowWidgetLibrary(true)}>
693
+ <Plus className="h-4 w-4 mr-2" />
694
+ Add Widget
695
+ </Button>
696
+ <Button variant="outline" onClick={() => setShowTemplates(true)}>
697
+ <LayoutGrid className="h-4 w-4 mr-2" />
698
+ Browse Templates
699
+ </Button>
700
+ </div>
701
+ </motion.div>
702
+ ) : (
703
+ // Dashboard grid
704
+ <DashboardGrid
705
+ widgets={widgets}
706
+ onLayoutChange={(layout) => {
707
+ // Update widget positions
708
+ const updatedWidgets = widgets.map(widget => {
709
+ const layoutItem = layout.find(l => l.i === widget.id)
710
+ if (layoutItem) {
711
+ return {
712
+ ...widget,
713
+ position: { x: layoutItem.x, y: layoutItem.y },
714
+ size: { ...widget.size, w: layoutItem.w, h: layoutItem.h }
715
+ }
716
+ }
717
+ return widget
718
+ })
719
+ setWidgets(updatedWidgets)
720
+ }}
721
+ onWidgetRemove={(widgetId) => {
722
+ setWidgets(prev => prev.filter(w => w.id !== widgetId))
723
+ onWidgetRemove?.(widgetId)
724
+ }}
725
+ onWidgetAction={(widgetId, action, data) => {
726
+ console.log('Widget action:', widgetId, action, data)
727
+ // Handle widget actions
728
+ }}
729
+ editMode={editMode}
730
+ glassmorphism={glassmorphism}
731
+ />
732
+ )}
329
733
  </div>
330
- </div>
734
+
735
+ {/* Modals */}
736
+ <WidgetLibrary />
737
+ <TemplateGallery />
738
+ </motion.div>
331
739
  )
332
740
  }
333
741