@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.
@@ -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
+ }