@moontra/moonui-pro 2.4.6 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,334 +1,750 @@
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
+ // initialWidgets değiştiğinde state'i güncelle
263
+ React.useEffect(() => {
264
+ setWidgets(initialWidgets)
265
+ }, [initialWidgets])
106
266
 
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()
267
+ // WebSocket bağlantısı (real-time için)
268
+ React.useEffect(() => {
269
+ if (!realtime) return
270
+
271
+ // Simüle edilmiş WebSocket bağlantısı
272
+ const interval = setInterval(() => {
273
+ // Widget'ları güncelle
274
+ setWidgets(prev => prev.map(widget => {
275
+ if (widget.type === 'metric' && widget.data) {
276
+ const newValue = typeof widget.data.value === 'number'
277
+ ? widget.data.value + Math.floor(Math.random() * 10 - 5)
278
+ : widget.data.value
279
+
280
+ return {
281
+ ...widget,
282
+ data: {
283
+ ...widget.data,
284
+ value: newValue,
285
+ lastUpdated: new Date()
286
+ }
287
+ }
288
+ }
289
+ return widget
290
+ }))
291
+ }, 5000)
292
+
293
+ return () => clearInterval(interval)
294
+ }, [realtime])
295
+
296
+ // Widget ekle
297
+ const handleAddWidget = (type: WidgetType) => {
298
+ const newWidget: Widget = {
299
+ id: `widget-${Date.now()}`,
300
+ type,
301
+ title: `New ${type} Widget`,
302
+ size: { w: 3, h: 3 },
303
+ position: { x: 0, y: 0 },
304
+ data: generateDefaultData(type)
115
305
  }
116
- return value.toString()
306
+
307
+ setWidgets(prev => [...prev, newWidget])
308
+ onWidgetAdd?.(newWidget)
309
+ setShowWidgetLibrary(false)
117
310
  }
118
311
 
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
- }
312
+ // Template uygula
313
+ const applyTemplate = (template: DashboardTemplate) => {
314
+ const newWidgets = template.widgets.map((w, index) => ({
315
+ ...w,
316
+ id: `widget-${Date.now()}-${index}`,
317
+ position: w.position || { x: (index * 3) % 12, y: Math.floor((index * 3) / 12) * 3 },
318
+ size: w.size || { w: 3, h: 3 }
319
+ }))
320
+
321
+ setWidgets(newWidgets as Widget[])
322
+ setSelectedTheme(template.theme)
323
+ setShowTemplates(false)
128
324
  }
129
325
 
130
- const getChangeColor = (type: 'increase' | 'decrease' | 'neutral') => {
326
+ // Varsayılan widget verisi oluştur
327
+ const generateDefaultData = (type: WidgetType): any => {
131
328
  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'
329
+ case 'metric':
330
+ return {
331
+ id: 'new-metric',
332
+ title: 'New Metric',
333
+ value: 0,
334
+ icon: <BarChart3 className="h-4 w-4" />,
335
+ color: 'primary'
336
+ }
337
+ case 'chart':
338
+ return {
339
+ type: 'line',
340
+ data: [
341
+ { name: 'Mon', value: 100 },
342
+ { name: 'Tue', value: 120 },
343
+ { name: 'Wed', value: 110 },
344
+ { name: 'Thu', value: 130 },
345
+ { name: 'Fri', value: 125 }
346
+ ]
347
+ }
348
+ case 'activity':
349
+ return {
350
+ items: [],
351
+ realtime: true
352
+ }
353
+ default:
354
+ return {}
138
355
  }
139
356
  }
140
357
 
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}
358
+ // Widget kütüphanesi
359
+ const WidgetLibrary = () => (
360
+ <Sheet open={showWidgetLibrary} onOpenChange={setShowWidgetLibrary}>
361
+ <SheetContent className={cn(
362
+ "w-[400px] sm:w-[540px]",
363
+ glassmorphism && "bg-background/95 backdrop-blur-md"
364
+ )}>
365
+ <SheetHeader>
366
+ <SheetTitle>Widget Library</SheetTitle>
367
+ <SheetDescription>
368
+ Add widgets to customize your dashboard
369
+ </SheetDescription>
370
+ </SheetHeader>
371
+
372
+ <div className="mt-6 space-y-4">
373
+ <div className="relative">
374
+ <Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
375
+ <Input
376
+ placeholder="Search widgets..."
377
+ value={searchQuery}
378
+ onChange={(e) => setSearchQuery(e.target.value)}
379
+ className="pl-10"
380
+ />
159
381
  </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
382
 
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>
383
+ <ScrollArea className="h-[calc(100vh-200px)]">
384
+ <div className="grid grid-cols-2 gap-4">
385
+ {Object.entries(WIDGET_TYPE_ICONS)
386
+ .filter(([type]) => type.toLowerCase().includes(searchQuery.toLowerCase()))
387
+ .map(([type, icon]) => (
388
+ <motion.div
389
+ key={type}
390
+ whileHover={{ scale: 1.02 }}
391
+ whileTap={{ scale: 0.98 }}
392
+ >
393
+ <Button
394
+ variant="outline"
395
+ className="w-full h-24 flex-col gap-2"
396
+ onClick={() => handleAddWidget(type as WidgetType)}
397
+ >
398
+ <div className="p-2 rounded-lg bg-muted">
399
+ {icon}
400
+ </div>
401
+ <span className="capitalize text-sm">{type}</span>
402
+ </Button>
403
+ </motion.div>
404
+ ))}
212
405
  </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
- }
406
+ </ScrollArea>
407
+ </div>
408
+ </SheetContent>
409
+ </Sheet>
410
+ )
224
411
 
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
- ]
412
+ // Template galerisi
413
+ const TemplateGallery = () => (
414
+ <Dialog open={showTemplates} onOpenChange={setShowTemplates}>
415
+ <DialogContent className={cn(
416
+ "max-w-4xl",
417
+ glassmorphism && "bg-background/95 backdrop-blur-md"
418
+ )}>
419
+ <DialogHeader>
420
+ <DialogTitle>Dashboard Templates</DialogTitle>
421
+ <DialogDescription>
422
+ Start with a pre-built template or create your own
423
+ </DialogDescription>
424
+ </DialogHeader>
259
425
 
260
- const displayMetrics = metrics.length > 0 ? metrics : defaultMetrics
426
+ <Tabs defaultValue="all" className="mt-4">
427
+ <TabsList>
428
+ <TabsTrigger value="all">All Templates</TabsTrigger>
429
+ <TabsTrigger value="analytics">Analytics</TabsTrigger>
430
+ <TabsTrigger value="sales">Sales</TabsTrigger>
431
+ <TabsTrigger value="operations">Operations</TabsTrigger>
432
+ </TabsList>
261
433
 
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
- }
434
+ <TabsContent value="all" className="mt-4">
435
+ <div className="grid grid-cols-3 gap-4">
436
+ {templates.map((template) => (
437
+ <motion.div
438
+ key={template.id}
439
+ whileHover={{ y: -4 }}
440
+ className="cursor-pointer"
441
+ onClick={() => applyTemplate(template)}
442
+ >
443
+ <div className={cn(
444
+ "relative overflow-hidden rounded-lg border p-4 hover:shadow-lg transition-all",
445
+ "bg-gradient-to-br", THEME_COLORS[template.theme]
446
+ )}>
447
+ {template.isPro && (
448
+ <Badge className="absolute top-2 right-2" variant="secondary">
449
+ PRO
450
+ </Badge>
451
+ )}
452
+ <h3 className="font-semibold mb-1">{template.name}</h3>
453
+ <p className="text-sm text-muted-foreground">
454
+ {template.description}
455
+ </p>
456
+ <div className="mt-3 flex items-center gap-2 text-xs text-muted-foreground">
457
+ <span>{template.widgets.length} widgets</span>
458
+ <span>•</span>
459
+ <span className="capitalize">{template.category}</span>
460
+ </div>
461
+ </div>
462
+ </motion.div>
463
+ ))}
464
+ </div>
465
+ </TabsContent>
466
+ </Tabs>
467
+ </DialogContent>
468
+ </Dialog>
469
+ )
276
470
 
277
471
  return (
278
- <div className={cn("w-full space-y-6", className)}>
472
+ <motion.div
473
+ className={cn(
474
+ "w-full min-h-screen",
475
+ glassmorphism && "bg-gradient-to-br",
476
+ glassmorphism && THEME_COLORS[selectedTheme],
477
+ className
478
+ )}
479
+ initial={{ opacity: 0 }}
480
+ animate={{ opacity: 1 }}
481
+ transition={{ duration: 0.5 }}
482
+ >
279
483
  {/* Header */}
280
484
  {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>
485
+ <motion.div
486
+ className={cn(
487
+ "sticky top-0 z-30 border-b",
488
+ glassmorphism ? "bg-background/80 backdrop-blur-md" : "bg-background"
299
489
  )}
300
- </div>
301
- )}
490
+ initial={{ y: -50 }}
491
+ animate={{ y: 0 }}
492
+ transition={{ type: "spring", stiffness: 300, damping: 30 }}
493
+ >
494
+ <div className="px-6 py-4">
495
+ <div className="flex items-center justify-between">
496
+ <div className="flex items-center gap-4">
497
+ <motion.div
498
+ whileHover={{ scale: 1.05 }}
499
+ whileTap={{ scale: 0.95 }}
500
+ >
501
+ <Button variant="ghost" size="sm" className="lg:hidden">
502
+ <Menu className="h-5 w-5" />
503
+ </Button>
504
+ </motion.div>
505
+
506
+ <div>
507
+ <div className="flex items-center gap-2">
508
+ <h1 className="text-2xl font-bold tracking-tight">{title}</h1>
509
+ {realtime && (
510
+ <Badge variant="secondary" className="animate-pulse">
511
+ <span className="mr-1 h-1.5 w-1.5 rounded-full bg-green-500" />
512
+ Real-time
513
+ </Badge>
514
+ )}
515
+ </div>
516
+ <p className="text-sm text-muted-foreground">{description}</p>
517
+ </div>
518
+ </div>
302
519
 
303
- {/* Metrics Grid */}
304
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
305
- {displayMetrics.map(renderMetricCard)}
306
- </div>
520
+ {/* Header actions */}
521
+ <div className="flex items-center gap-2">
522
+ {/* Time range picker */}
523
+ <TimeRangePicker
524
+ value={timeRange}
525
+ onChange={setTimeRange}
526
+ glassmorphism={glassmorphism}
527
+ />
307
528
 
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>
529
+ {/* Notifications */}
530
+ <div className="relative">
531
+ <Button variant="ghost" size="sm" className="relative">
532
+ <Bell className="h-4 w-4" />
533
+ {notifications > 0 && (
534
+ <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">
535
+ {notifications}
536
+ </span>
537
+ )}
538
+ </Button>
539
+ </div>
540
+
541
+ {/* Theme selector */}
542
+ <DropdownMenu>
543
+ <DropdownMenuTrigger asChild>
544
+ <Button variant="ghost" size="sm">
545
+ <Palette className="h-4 w-4" />
546
+ </Button>
547
+ </DropdownMenuTrigger>
548
+ <DropdownMenuContent align="end">
549
+ <DropdownMenuItem onClick={() => setSelectedTheme('analytics')}>
550
+ <div className="flex items-center gap-2">
551
+ <div className="h-4 w-4 rounded bg-gradient-to-br from-blue-500 to-purple-500" />
552
+ Analytics
553
+ </div>
554
+ </DropdownMenuItem>
555
+ <DropdownMenuItem onClick={() => setSelectedTheme('sales')}>
556
+ <div className="flex items-center gap-2">
557
+ <div className="h-4 w-4 rounded bg-gradient-to-br from-green-500 to-emerald-500" />
558
+ Sales
559
+ </div>
560
+ </DropdownMenuItem>
561
+ <DropdownMenuItem onClick={() => setSelectedTheme('monitoring')}>
562
+ <div className="flex items-center gap-2">
563
+ <div className="h-4 w-4 rounded bg-gradient-to-br from-orange-500 to-red-500" />
564
+ Monitoring
565
+ </div>
566
+ </DropdownMenuItem>
567
+ </DropdownMenuContent>
568
+ </DropdownMenu>
569
+
570
+ {/* More actions */}
571
+ <DropdownMenu>
572
+ <DropdownMenuTrigger asChild>
573
+ <Button variant="outline" size="sm">
574
+ <Settings className="h-4 w-4 mr-2" />
575
+ Actions
576
+ </Button>
577
+ </DropdownMenuTrigger>
578
+ <DropdownMenuContent align="end" className="w-48">
579
+ {editable && (
580
+ <>
581
+ <DropdownMenuItem onClick={() => setEditMode(!editMode)}>
582
+ <Edit3 className="mr-2 h-4 w-4" />
583
+ {editMode ? 'Exit Edit Mode' : 'Edit Layout'}
584
+ </DropdownMenuItem>
585
+ <DropdownMenuItem onClick={() => setShowWidgetLibrary(true)}>
586
+ <Plus className="mr-2 h-4 w-4" />
587
+ Add Widget
588
+ </DropdownMenuItem>
589
+ <DropdownMenuItem onClick={() => setShowTemplates(true)}>
590
+ <LayoutGrid className="mr-2 h-4 w-4" />
591
+ Templates
592
+ </DropdownMenuItem>
593
+ <DropdownMenuSeparator />
594
+ </>
595
+ )}
596
+
597
+ <DropdownMenuSub>
598
+ <DropdownMenuSubTrigger>
599
+ <Download className="mr-2 h-4 w-4" />
600
+ Export
601
+ </DropdownMenuSubTrigger>
602
+ <DropdownMenuSubContent>
603
+ <DropdownMenuItem onClick={() => onExport?.('json')}>
604
+ As JSON
605
+ </DropdownMenuItem>
606
+ <DropdownMenuItem onClick={() => onExport?.('pdf')}>
607
+ As PDF
608
+ </DropdownMenuItem>
609
+ <DropdownMenuItem onClick={() => onExport?.('png')}>
610
+ As Image
611
+ </DropdownMenuItem>
612
+ </DropdownMenuSubContent>
613
+ </DropdownMenuSub>
614
+
615
+ <DropdownMenuItem>
616
+ <Upload className="mr-2 h-4 w-4" />
617
+ Import
618
+ </DropdownMenuItem>
619
+
620
+ <DropdownMenuSeparator />
621
+
622
+ <DropdownMenuItem onClick={() => setRefreshing(true)}>
623
+ <RefreshCw className="mr-2 h-4 w-4" />
624
+ Refresh Data
625
+ </DropdownMenuItem>
626
+
627
+ <DropdownMenuItem>
628
+ <Share2 className="mr-2 h-4 w-4" />
629
+ Share Dashboard
630
+ </DropdownMenuItem>
631
+ </DropdownMenuContent>
632
+ </DropdownMenu>
633
+ </div>
634
+ </div>
635
+ </div>
636
+
637
+ {/* Edit mode banner */}
638
+ <AnimatePresence>
639
+ {editMode && (
640
+ <motion.div
641
+ initial={{ height: 0, opacity: 0 }}
642
+ animate={{ height: "auto", opacity: 1 }}
643
+ exit={{ height: 0, opacity: 0 }}
644
+ className="bg-primary/10 border-t border-primary/20"
645
+ >
646
+ <div className="px-6 py-2 flex items-center justify-between">
647
+ <div className="flex items-center gap-2 text-sm">
648
+ <Sparkles className="h-4 w-4 text-primary" />
649
+ <span>Edit mode enabled - Drag widgets to rearrange</span>
650
+ </div>
651
+ <div className="flex items-center gap-2">
652
+ <Button
653
+ variant="ghost"
654
+ size="sm"
655
+ onClick={() => setEditMode(false)}
656
+ >
657
+ <X className="h-4 w-4 mr-1" />
658
+ Cancel
659
+ </Button>
660
+ <Button
661
+ variant="primary"
662
+ size="sm"
663
+ onClick={() => {
664
+ setEditMode(false)
665
+ // Save layout changes
666
+ }}
667
+ >
668
+ <Save className="h-4 w-4 mr-1" />
669
+ Save Changes
670
+ </Button>
671
+ </div>
672
+ </div>
673
+ </motion.div>
674
+ )}
675
+ </AnimatePresence>
676
+ </motion.div>
313
677
  )}
314
678
 
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>
679
+ {/* Main content */}
680
+ <div className="p-6">
681
+ {widgets.length === 0 ? (
682
+ // Empty state
683
+ <motion.div
684
+ initial={{ opacity: 0, scale: 0.9 }}
685
+ animate={{ opacity: 1, scale: 1 }}
686
+ className="flex flex-col items-center justify-center min-h-[400px] text-center"
687
+ >
688
+ <div className={cn(
689
+ "p-4 rounded-full mb-4",
690
+ glassmorphism ? "bg-background/60 backdrop-blur-sm" : "bg-muted"
691
+ )}>
692
+ <LayoutGrid className="h-8 w-8 text-muted-foreground" />
693
+ </div>
694
+ <h3 className="text-lg font-semibold mb-2">No widgets added yet</h3>
695
+ <p className="text-muted-foreground mb-4">
696
+ Start by adding widgets or choosing a template
697
+ </p>
698
+ <div className="flex gap-2">
699
+ <Button onClick={() => setShowWidgetLibrary(true)}>
700
+ <Plus className="h-4 w-4 mr-2" />
701
+ Add Widget
702
+ </Button>
703
+ <Button variant="outline" onClick={() => setShowTemplates(true)}>
704
+ <LayoutGrid className="h-4 w-4 mr-2" />
705
+ Browse Templates
706
+ </Button>
707
+ </div>
708
+ </motion.div>
709
+ ) : (
710
+ // Dashboard grid
711
+ <DashboardGrid
712
+ widgets={widgets}
713
+ onLayoutChange={(layout) => {
714
+ // Update widget positions
715
+ const updatedWidgets = widgets.map(widget => {
716
+ const layoutItem = layout.find(l => l.i === widget.id)
717
+ if (layoutItem) {
718
+ return {
719
+ ...widget,
720
+ position: { x: layoutItem.x, y: layoutItem.y },
721
+ size: { ...widget.size, w: layoutItem.w, h: layoutItem.h }
722
+ }
723
+ }
724
+ return widget
725
+ })
726
+ setWidgets(updatedWidgets)
727
+ }}
728
+ onWidgetRemove={(widgetId) => {
729
+ setWidgets(prev => prev.filter(w => w.id !== widgetId))
730
+ onWidgetRemove?.(widgetId)
731
+ }}
732
+ onWidgetAction={(widgetId, action, data) => {
733
+ console.log('Widget action:', widgetId, action, data)
734
+ // Handle widget actions
735
+ }}
736
+ editMode={editMode}
737
+ glassmorphism={glassmorphism}
738
+ />
739
+ )}
329
740
  </div>
330
- </div>
741
+
742
+ {/* Modals */}
743
+ <WidgetLibrary />
744
+ <TemplateGallery />
745
+ </motion.div>
331
746
  )
332
747
  }
333
748
 
749
+ export type { Widget, MetricData, ChartData, ActivityItem, ProgressData, ComparisonData } from './types'
334
750
  export default Dashboard