@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.
- package/dist/index.d.ts +127 -25
- package/dist/index.mjs +2023 -186
- package/package.json +4 -1
- package/src/components/advanced-chart/index.tsx +11 -1
- package/src/components/dashboard/dashboard-grid.tsx +327 -0
- package/src/components/dashboard/demo.tsx +308 -0
- package/src/components/dashboard/index.tsx +683 -275
- package/src/components/dashboard/time-range-picker.tsx +267 -0
- package/src/components/dashboard/types.ts +222 -0
- package/src/components/dashboard/widgets/activity-feed.tsx +344 -0
- package/src/components/dashboard/widgets/chart-widget.tsx +418 -0
- package/src/components/dashboard/widgets/metric-card.tsx +343 -0
- package/src/components/index.ts +2 -2
- package/src/components/ui/calendar.tsx +65 -0
- package/src/components/ui/index.ts +12 -0
- package/src/components/ui/scroll-area.tsx +47 -0
- package/src/components/ui/sheet.tsx +139 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { motion } from 'framer-motion'
|
|
5
|
+
import { Button } from '../ui/button'
|
|
6
|
+
import { Calendar } from '../ui/calendar'
|
|
7
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
|
9
|
+
import { cn } from '../../lib/utils'
|
|
10
|
+
import { CalendarIcon, Clock, ChevronDown } from 'lucide-react'
|
|
11
|
+
import { format, startOfDay, endOfDay, subDays, startOfWeek, endOfWeek, startOfMonth, endOfMonth } from 'date-fns'
|
|
12
|
+
import { TimeRange } from './types'
|
|
13
|
+
|
|
14
|
+
interface TimeRangePickerProps {
|
|
15
|
+
value?: TimeRange
|
|
16
|
+
onChange?: (range: TimeRange) => void
|
|
17
|
+
className?: string
|
|
18
|
+
showPresets?: boolean
|
|
19
|
+
showComparison?: boolean
|
|
20
|
+
glassmorphism?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PRESET_RANGES = [
|
|
24
|
+
{
|
|
25
|
+
label: 'Today',
|
|
26
|
+
value: 'today',
|
|
27
|
+
getRange: () => ({
|
|
28
|
+
start: startOfDay(new Date()),
|
|
29
|
+
end: endOfDay(new Date()),
|
|
30
|
+
label: 'Today',
|
|
31
|
+
preset: 'today' as const
|
|
32
|
+
})
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
label: 'Yesterday',
|
|
36
|
+
value: 'yesterday',
|
|
37
|
+
getRange: () => ({
|
|
38
|
+
start: startOfDay(subDays(new Date(), 1)),
|
|
39
|
+
end: endOfDay(subDays(new Date(), 1)),
|
|
40
|
+
label: 'Yesterday',
|
|
41
|
+
preset: 'yesterday' as const
|
|
42
|
+
})
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: 'Last 7 days',
|
|
46
|
+
value: 'last7days',
|
|
47
|
+
getRange: () => ({
|
|
48
|
+
start: startOfDay(subDays(new Date(), 6)),
|
|
49
|
+
end: endOfDay(new Date()),
|
|
50
|
+
label: 'Last 7 days',
|
|
51
|
+
preset: 'last7days' as const
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: 'Last 30 days',
|
|
56
|
+
value: 'last30days',
|
|
57
|
+
getRange: () => ({
|
|
58
|
+
start: startOfDay(subDays(new Date(), 29)),
|
|
59
|
+
end: endOfDay(new Date()),
|
|
60
|
+
label: 'Last 30 days',
|
|
61
|
+
preset: 'last30days' as const
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
label: 'This week',
|
|
66
|
+
value: 'thisWeek',
|
|
67
|
+
getRange: () => ({
|
|
68
|
+
start: startOfWeek(new Date()),
|
|
69
|
+
end: endOfWeek(new Date()),
|
|
70
|
+
label: 'This week',
|
|
71
|
+
preset: 'custom' as const
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
label: 'This month',
|
|
76
|
+
value: 'thisMonth',
|
|
77
|
+
getRange: () => ({
|
|
78
|
+
start: startOfMonth(new Date()),
|
|
79
|
+
end: endOfMonth(new Date()),
|
|
80
|
+
label: 'This month',
|
|
81
|
+
preset: 'thisMonth' as const
|
|
82
|
+
})
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'Last month',
|
|
86
|
+
value: 'lastMonth',
|
|
87
|
+
getRange: () => {
|
|
88
|
+
const lastMonth = subDays(startOfMonth(new Date()), 1)
|
|
89
|
+
return {
|
|
90
|
+
start: startOfMonth(lastMonth),
|
|
91
|
+
end: endOfMonth(lastMonth),
|
|
92
|
+
label: 'Last month',
|
|
93
|
+
preset: 'lastMonth' as const
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
export function TimeRangePicker({
|
|
100
|
+
value,
|
|
101
|
+
onChange,
|
|
102
|
+
className,
|
|
103
|
+
showPresets = true,
|
|
104
|
+
showComparison = false,
|
|
105
|
+
glassmorphism = false
|
|
106
|
+
}: TimeRangePickerProps) {
|
|
107
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
108
|
+
const [customRange, setCustomRange] = React.useState<{ from?: Date; to?: Date }>({})
|
|
109
|
+
const [comparisonEnabled, setComparisonEnabled] = React.useState(false)
|
|
110
|
+
|
|
111
|
+
const currentRange = value || PRESET_RANGES[2].getRange() // Default: Last 7 days
|
|
112
|
+
|
|
113
|
+
// Format tarih aralığı
|
|
114
|
+
const formatRange = (range: TimeRange) => {
|
|
115
|
+
if (range.preset && range.preset !== 'custom') {
|
|
116
|
+
return range.label
|
|
117
|
+
}
|
|
118
|
+
return `${format(range.start, 'MMM d')} - ${format(range.end, 'MMM d, yyyy')}`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Preset seçimi
|
|
122
|
+
const handlePresetSelect = (preset: typeof PRESET_RANGES[0]) => {
|
|
123
|
+
const range = preset.getRange()
|
|
124
|
+
onChange?.(range)
|
|
125
|
+
setIsOpen(false)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Custom tarih seçimi
|
|
129
|
+
const handleCustomRangeSelect = () => {
|
|
130
|
+
if (customRange.from && customRange.to) {
|
|
131
|
+
onChange?.({
|
|
132
|
+
start: startOfDay(customRange.from),
|
|
133
|
+
end: endOfDay(customRange.to),
|
|
134
|
+
label: `${format(customRange.from, 'MMM d')} - ${format(customRange.to, 'MMM d, yyyy')}`,
|
|
135
|
+
preset: 'custom'
|
|
136
|
+
})
|
|
137
|
+
setIsOpen(false)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
|
143
|
+
<PopoverTrigger asChild>
|
|
144
|
+
<Button
|
|
145
|
+
variant="outline"
|
|
146
|
+
className={cn(
|
|
147
|
+
"justify-start text-left font-normal",
|
|
148
|
+
glassmorphism && "bg-background/60 backdrop-blur-sm border-white/10",
|
|
149
|
+
!value && "text-muted-foreground",
|
|
150
|
+
className
|
|
151
|
+
)}
|
|
152
|
+
>
|
|
153
|
+
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
154
|
+
{formatRange(currentRange)}
|
|
155
|
+
<ChevronDown className="ml-auto h-4 w-4 opacity-50" />
|
|
156
|
+
</Button>
|
|
157
|
+
</PopoverTrigger>
|
|
158
|
+
<PopoverContent
|
|
159
|
+
className={cn(
|
|
160
|
+
"w-auto p-0",
|
|
161
|
+
glassmorphism && "bg-background/95 backdrop-blur-md border-white/10"
|
|
162
|
+
)}
|
|
163
|
+
align="start"
|
|
164
|
+
>
|
|
165
|
+
<motion.div
|
|
166
|
+
initial={{ opacity: 0, y: -10 }}
|
|
167
|
+
animate={{ opacity: 1, y: 0 }}
|
|
168
|
+
transition={{ duration: 0.2 }}
|
|
169
|
+
>
|
|
170
|
+
<div className="flex">
|
|
171
|
+
{/* Preset'ler */}
|
|
172
|
+
{showPresets && (
|
|
173
|
+
<div className="border-r p-3">
|
|
174
|
+
<div className="flex items-center gap-2 px-2 pb-2">
|
|
175
|
+
<Clock className="h-4 w-4 text-muted-foreground" />
|
|
176
|
+
<h4 className="text-sm font-medium">Quick Select</h4>
|
|
177
|
+
</div>
|
|
178
|
+
<div className="space-y-1">
|
|
179
|
+
{PRESET_RANGES.map((preset) => (
|
|
180
|
+
<motion.button
|
|
181
|
+
key={preset.value}
|
|
182
|
+
whileHover={{ x: 2 }}
|
|
183
|
+
whileTap={{ scale: 0.98 }}
|
|
184
|
+
onClick={() => handlePresetSelect(preset)}
|
|
185
|
+
className={cn(
|
|
186
|
+
"w-full text-left px-2 py-1.5 text-sm rounded-md transition-colors",
|
|
187
|
+
"hover:bg-muted",
|
|
188
|
+
currentRange.preset === preset.value && "bg-primary text-primary-foreground"
|
|
189
|
+
)}
|
|
190
|
+
>
|
|
191
|
+
{preset.label}
|
|
192
|
+
</motion.button>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
{/* Karşılaştırma */}
|
|
197
|
+
{showComparison && (
|
|
198
|
+
<div className="mt-4 pt-4 border-t">
|
|
199
|
+
<label className="flex items-center gap-2 px-2 cursor-pointer">
|
|
200
|
+
<input
|
|
201
|
+
type="checkbox"
|
|
202
|
+
checked={comparisonEnabled}
|
|
203
|
+
onChange={(e) => setComparisonEnabled(e.target.checked)}
|
|
204
|
+
className="rounded"
|
|
205
|
+
/>
|
|
206
|
+
<span className="text-sm">Compare to previous period</span>
|
|
207
|
+
</label>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{/* Custom tarih seçici */}
|
|
214
|
+
<div className="p-3">
|
|
215
|
+
<div className="flex items-center gap-2 px-2 pb-2">
|
|
216
|
+
<CalendarIcon className="h-4 w-4 text-muted-foreground" />
|
|
217
|
+
<h4 className="text-sm font-medium">Custom Range</h4>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<Calendar
|
|
221
|
+
mode="range"
|
|
222
|
+
selected={{
|
|
223
|
+
from: customRange.from,
|
|
224
|
+
to: customRange.to
|
|
225
|
+
}}
|
|
226
|
+
onSelect={(range: any) => setCustomRange(range || {})}
|
|
227
|
+
numberOfMonths={2}
|
|
228
|
+
className="rounded-md"
|
|
229
|
+
/>
|
|
230
|
+
|
|
231
|
+
<div className="flex items-center justify-between pt-3 px-2">
|
|
232
|
+
<div className="text-sm text-muted-foreground">
|
|
233
|
+
{customRange.from && customRange.to && (
|
|
234
|
+
<span>
|
|
235
|
+
{Math.ceil((customRange.to.getTime() - customRange.from.getTime()) / (1000 * 60 * 60 * 24))} days selected
|
|
236
|
+
</span>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
<div className="flex gap-2">
|
|
240
|
+
<Button
|
|
241
|
+
variant="ghost"
|
|
242
|
+
size="sm"
|
|
243
|
+
onClick={() => {
|
|
244
|
+
setCustomRange({})
|
|
245
|
+
setIsOpen(false)
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
Cancel
|
|
249
|
+
</Button>
|
|
250
|
+
<Button
|
|
251
|
+
size="sm"
|
|
252
|
+
onClick={handleCustomRangeSelect}
|
|
253
|
+
disabled={!customRange.from || !customRange.to}
|
|
254
|
+
>
|
|
255
|
+
Apply
|
|
256
|
+
</Button>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</motion.div>
|
|
262
|
+
</PopoverContent>
|
|
263
|
+
</Popover>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export default TimeRangePicker
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
// Widget tipleri
|
|
4
|
+
export type WidgetType =
|
|
5
|
+
| 'metric'
|
|
6
|
+
| 'chart'
|
|
7
|
+
| 'table'
|
|
8
|
+
| 'map'
|
|
9
|
+
| 'activity'
|
|
10
|
+
| 'calendar'
|
|
11
|
+
| 'progress'
|
|
12
|
+
| 'comparison'
|
|
13
|
+
|
|
14
|
+
// Widget boyutları
|
|
15
|
+
export interface WidgetSize {
|
|
16
|
+
w: number // Grid genişliği
|
|
17
|
+
h: number // Grid yüksekliği
|
|
18
|
+
minW?: number
|
|
19
|
+
maxW?: number
|
|
20
|
+
minH?: number
|
|
21
|
+
maxH?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Widget pozisyonu
|
|
25
|
+
export interface WidgetPosition {
|
|
26
|
+
x: number
|
|
27
|
+
y: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Tema tipleri
|
|
31
|
+
export type DashboardTheme = 'analytics' | 'sales' | 'monitoring' | 'finance' | 'custom'
|
|
32
|
+
|
|
33
|
+
// Zaman aralığı
|
|
34
|
+
export interface TimeRange {
|
|
35
|
+
start: Date
|
|
36
|
+
end: Date
|
|
37
|
+
label: string
|
|
38
|
+
preset?: 'today' | 'yesterday' | 'last7days' | 'last30days' | 'thisMonth' | 'lastMonth' | 'custom'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Metric widget
|
|
42
|
+
export interface MetricData {
|
|
43
|
+
id: string
|
|
44
|
+
title: string
|
|
45
|
+
value: string | number
|
|
46
|
+
change?: {
|
|
47
|
+
value: number
|
|
48
|
+
type: 'increase' | 'decrease' | 'neutral'
|
|
49
|
+
period: string
|
|
50
|
+
}
|
|
51
|
+
icon?: ReactNode
|
|
52
|
+
color?: 'primary' | 'success' | 'warning' | 'danger' | 'info'
|
|
53
|
+
sparkline?: number[]
|
|
54
|
+
forecast?: number
|
|
55
|
+
target?: number
|
|
56
|
+
unit?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Chart widget
|
|
60
|
+
export interface ChartData {
|
|
61
|
+
type: 'line' | 'bar' | 'area' | 'pie' | 'donut' | 'radar'
|
|
62
|
+
data: any
|
|
63
|
+
options?: any
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Table widget
|
|
67
|
+
export interface TableData {
|
|
68
|
+
columns: Array<{
|
|
69
|
+
id: string
|
|
70
|
+
header: string
|
|
71
|
+
accessor: string
|
|
72
|
+
sortable?: boolean
|
|
73
|
+
filterable?: boolean
|
|
74
|
+
}>
|
|
75
|
+
data: any[]
|
|
76
|
+
pagination?: boolean
|
|
77
|
+
pageSize?: number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Activity widget
|
|
81
|
+
export interface ActivityItem {
|
|
82
|
+
id: string
|
|
83
|
+
type: 'info' | 'success' | 'warning' | 'error'
|
|
84
|
+
title: string
|
|
85
|
+
description?: string
|
|
86
|
+
timestamp: Date
|
|
87
|
+
user?: {
|
|
88
|
+
name: string
|
|
89
|
+
avatar?: string
|
|
90
|
+
}
|
|
91
|
+
icon?: ReactNode
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Progress widget
|
|
95
|
+
export interface ProgressData {
|
|
96
|
+
id: string
|
|
97
|
+
title: string
|
|
98
|
+
current: number
|
|
99
|
+
target: number
|
|
100
|
+
unit?: string
|
|
101
|
+
color?: 'primary' | 'success' | 'warning' | 'danger'
|
|
102
|
+
deadline?: Date
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Comparison widget
|
|
106
|
+
export interface ComparisonData {
|
|
107
|
+
periods: Array<{
|
|
108
|
+
label: string
|
|
109
|
+
value: number
|
|
110
|
+
data?: any[]
|
|
111
|
+
}>
|
|
112
|
+
metric: string
|
|
113
|
+
unit?: string
|
|
114
|
+
showChart?: boolean
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Widget base interface
|
|
118
|
+
export interface Widget {
|
|
119
|
+
id: string
|
|
120
|
+
type: WidgetType
|
|
121
|
+
title: string
|
|
122
|
+
description?: string
|
|
123
|
+
size: WidgetSize
|
|
124
|
+
position: WidgetPosition
|
|
125
|
+
data?: any
|
|
126
|
+
config?: any
|
|
127
|
+
loading?: boolean
|
|
128
|
+
error?: string
|
|
129
|
+
refreshInterval?: number // ms
|
|
130
|
+
lastUpdated?: Date
|
|
131
|
+
permissions?: {
|
|
132
|
+
canEdit?: boolean
|
|
133
|
+
canDelete?: boolean
|
|
134
|
+
canResize?: boolean
|
|
135
|
+
canMove?: boolean
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Dashboard configuration
|
|
140
|
+
export interface DashboardConfig {
|
|
141
|
+
id: string
|
|
142
|
+
name: string
|
|
143
|
+
description?: string
|
|
144
|
+
theme: DashboardTheme
|
|
145
|
+
layout: {
|
|
146
|
+
rowHeight: number
|
|
147
|
+
margin: [number, number]
|
|
148
|
+
containerPadding: [number, number]
|
|
149
|
+
breakpoints: {
|
|
150
|
+
lg: number
|
|
151
|
+
md: number
|
|
152
|
+
sm: number
|
|
153
|
+
xs: number
|
|
154
|
+
xxs: number
|
|
155
|
+
}
|
|
156
|
+
cols: {
|
|
157
|
+
lg: number
|
|
158
|
+
md: number
|
|
159
|
+
sm: number
|
|
160
|
+
xs: number
|
|
161
|
+
xxs: number
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
widgets: Widget[]
|
|
165
|
+
filters?: {
|
|
166
|
+
timeRange?: TimeRange
|
|
167
|
+
dimensions?: Record<string, any>
|
|
168
|
+
}
|
|
169
|
+
createdAt: Date
|
|
170
|
+
updatedAt: Date
|
|
171
|
+
createdBy?: string
|
|
172
|
+
shared?: boolean
|
|
173
|
+
tags?: string[]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Dashboard template
|
|
177
|
+
export interface DashboardTemplate {
|
|
178
|
+
id: string
|
|
179
|
+
name: string
|
|
180
|
+
description: string
|
|
181
|
+
thumbnail?: string
|
|
182
|
+
category: 'analytics' | 'sales' | 'operations' | 'finance' | 'marketing' | 'custom'
|
|
183
|
+
widgets: Omit<Widget, 'id'>[]
|
|
184
|
+
theme: DashboardTheme
|
|
185
|
+
popularity?: number
|
|
186
|
+
isPro?: boolean
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// WebSocket message
|
|
190
|
+
export interface DashboardUpdate {
|
|
191
|
+
type: 'widget-update' | 'notification' | 'alert' | 'refresh'
|
|
192
|
+
widgetId?: string
|
|
193
|
+
data?: any
|
|
194
|
+
timestamp: Date
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Export format
|
|
198
|
+
export type ExportFormat = 'json' | 'csv' | 'pdf' | 'png'
|
|
199
|
+
|
|
200
|
+
// Filter types
|
|
201
|
+
export interface DashboardFilter {
|
|
202
|
+
id: string
|
|
203
|
+
type: 'select' | 'multiselect' | 'daterange' | 'search' | 'toggle'
|
|
204
|
+
label: string
|
|
205
|
+
field: string
|
|
206
|
+
options?: Array<{ value: any; label: string }>
|
|
207
|
+
defaultValue?: any
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Notification
|
|
211
|
+
export interface DashboardNotification {
|
|
212
|
+
id: string
|
|
213
|
+
type: 'info' | 'success' | 'warning' | 'error'
|
|
214
|
+
title: string
|
|
215
|
+
message?: string
|
|
216
|
+
timestamp: Date
|
|
217
|
+
read?: boolean
|
|
218
|
+
action?: {
|
|
219
|
+
label: string
|
|
220
|
+
handler: () => void
|
|
221
|
+
}
|
|
222
|
+
}
|