@moontra/moonui-pro 2.20.2 → 2.20.3
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 +176 -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,363 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { motion } from 'framer-motion'
|
|
5
|
-
import { Card } from '../../ui/card'
|
|
6
|
-
import { cn } from '../../../lib/utils'
|
|
7
|
-
import {
|
|
8
|
-
TrendingUp,
|
|
9
|
-
TrendingDown,
|
|
10
|
-
Minus,
|
|
11
|
-
ArrowUpRight,
|
|
12
|
-
ArrowDownRight,
|
|
13
|
-
MoreVertical,
|
|
14
|
-
Maximize2,
|
|
15
|
-
Download,
|
|
16
|
-
Share2
|
|
17
|
-
} from 'lucide-react'
|
|
18
|
-
import { MetricData } from '../types'
|
|
19
|
-
import { Button } from '../../ui/button'
|
|
20
|
-
import {
|
|
21
|
-
DropdownMenu,
|
|
22
|
-
DropdownMenuContent,
|
|
23
|
-
DropdownMenuItem,
|
|
24
|
-
DropdownMenuTrigger,
|
|
25
|
-
} from '../../ui/dropdown-menu'
|
|
26
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip'
|
|
27
|
-
|
|
28
|
-
interface MetricCardProps {
|
|
29
|
-
data: MetricData
|
|
30
|
-
onClick?: () => void
|
|
31
|
-
onAction?: (action: string) => void
|
|
32
|
-
className?: string
|
|
33
|
-
showSparkline?: boolean
|
|
34
|
-
showForecast?: boolean
|
|
35
|
-
interactive?: boolean
|
|
36
|
-
glassmorphism?: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function MetricCard({
|
|
40
|
-
data,
|
|
41
|
-
onClick,
|
|
42
|
-
onAction,
|
|
43
|
-
className,
|
|
44
|
-
showSparkline = true,
|
|
45
|
-
showForecast = false,
|
|
46
|
-
interactive = true,
|
|
47
|
-
glassmorphism = false
|
|
48
|
-
}: MetricCardProps) {
|
|
49
|
-
const [isHovered, setIsHovered] = React.useState(false)
|
|
50
|
-
const [isMounted, setIsMounted] = React.useState(false)
|
|
51
|
-
|
|
52
|
-
React.useEffect(() => {
|
|
53
|
-
setIsMounted(true)
|
|
54
|
-
}, [])
|
|
55
|
-
|
|
56
|
-
// Renk sınıfları
|
|
57
|
-
const colorClasses = {
|
|
58
|
-
primary: 'text-blue-600 dark:text-blue-400',
|
|
59
|
-
success: 'text-green-600 dark:text-green-400',
|
|
60
|
-
warning: 'text-yellow-600 dark:text-yellow-400',
|
|
61
|
-
danger: 'text-red-600 dark:text-red-400',
|
|
62
|
-
info: 'text-purple-600 dark:text-purple-400'
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Değişim renkleri
|
|
66
|
-
const getChangeColor = (type: 'increase' | 'decrease' | 'neutral') => {
|
|
67
|
-
switch (type) {
|
|
68
|
-
case 'increase':
|
|
69
|
-
return 'text-green-600 dark:text-green-400'
|
|
70
|
-
case 'decrease':
|
|
71
|
-
return 'text-red-600 dark:text-red-400'
|
|
72
|
-
default:
|
|
73
|
-
return 'text-muted-foreground'
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Değişim ikonu
|
|
78
|
-
const getChangeIcon = (type: 'increase' | 'decrease' | 'neutral') => {
|
|
79
|
-
switch (type) {
|
|
80
|
-
case 'increase':
|
|
81
|
-
return <ArrowUpRight className="h-4 w-4" />
|
|
82
|
-
case 'decrease':
|
|
83
|
-
return <ArrowDownRight className="h-4 w-4" />
|
|
84
|
-
default:
|
|
85
|
-
return <Minus className="h-4 w-4" />
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Sparkline çizimi
|
|
90
|
-
const renderSparkline = () => {
|
|
91
|
-
if (!data.sparkline || data.sparkline.length < 2) return null
|
|
92
|
-
|
|
93
|
-
const max = Math.max(...data.sparkline)
|
|
94
|
-
const min = Math.min(...data.sparkline)
|
|
95
|
-
const range = max - min || 1
|
|
96
|
-
|
|
97
|
-
const points = data.sparkline
|
|
98
|
-
.map((value, index) => {
|
|
99
|
-
const x = (index / (data.sparkline!.length - 1)) * 100
|
|
100
|
-
const y = 100 - ((value - min) / range) * 100
|
|
101
|
-
return `${x},${y}`
|
|
102
|
-
})
|
|
103
|
-
.join(' ')
|
|
104
|
-
|
|
105
|
-
const gradientId = `gradient-${data.id}`
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<motion.div
|
|
109
|
-
className="absolute bottom-0 left-0 right-0 h-16 overflow-hidden rounded-b-lg opacity-20"
|
|
110
|
-
initial={{ opacity: 0 }}
|
|
111
|
-
animate={{ opacity: isHovered ? 0.3 : 0.2 }}
|
|
112
|
-
transition={{ duration: 0.2 }}
|
|
113
|
-
>
|
|
114
|
-
<svg
|
|
115
|
-
className="w-full h-full"
|
|
116
|
-
viewBox="0 0 100 100"
|
|
117
|
-
preserveAspectRatio="none"
|
|
118
|
-
>
|
|
119
|
-
<defs>
|
|
120
|
-
<linearGradient id={gradientId} x1="0%" y1="0%" x2="0%" y2="100%">
|
|
121
|
-
<stop offset="0%" stopColor="currentColor" stopOpacity="0.3" />
|
|
122
|
-
<stop offset="100%" stopColor="currentColor" stopOpacity="0" />
|
|
123
|
-
</linearGradient>
|
|
124
|
-
</defs>
|
|
125
|
-
<polyline
|
|
126
|
-
fill="none"
|
|
127
|
-
stroke="currentColor"
|
|
128
|
-
strokeWidth="2"
|
|
129
|
-
points={points}
|
|
130
|
-
className={colorClasses[data.color || 'primary']}
|
|
131
|
-
/>
|
|
132
|
-
<polygon
|
|
133
|
-
fill={`url(#${gradientId})`}
|
|
134
|
-
points={`0,100 ${points} 100,100`}
|
|
135
|
-
className={colorClasses[data.color || 'primary']}
|
|
136
|
-
/>
|
|
137
|
-
</svg>
|
|
138
|
-
</motion.div>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// İlerleme barı (hedef varsa)
|
|
143
|
-
const renderProgress = () => {
|
|
144
|
-
if (!data.target) return null
|
|
145
|
-
|
|
146
|
-
const currentValue = typeof data.value === 'number' ? data.value : parseFloat(data.value as string)
|
|
147
|
-
const progress = (currentValue / data.target) * 100
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<motion.div
|
|
151
|
-
className="mt-3"
|
|
152
|
-
initial={{ opacity: 0, y: 10 }}
|
|
153
|
-
animate={{ opacity: 1, y: 0 }}
|
|
154
|
-
transition={{ delay: 0.1 }}
|
|
155
|
-
>
|
|
156
|
-
<div className="flex justify-between text-xs text-muted-foreground mb-1">
|
|
157
|
-
<span>Progress to target</span>
|
|
158
|
-
<span>{progress.toFixed(1)}%</span>
|
|
159
|
-
</div>
|
|
160
|
-
<div className="h-1.5 bg-muted rounded-full overflow-hidden">
|
|
161
|
-
<motion.div
|
|
162
|
-
className={cn(
|
|
163
|
-
"h-full rounded-full",
|
|
164
|
-
progress >= 100 ? 'bg-green-500' : 'bg-primary'
|
|
165
|
-
)}
|
|
166
|
-
initial={{ width: 0 }}
|
|
167
|
-
animate={{ width: `${Math.min(progress, 100)}%` }}
|
|
168
|
-
transition={{ duration: 0.5, ease: "easeOut" }}
|
|
169
|
-
/>
|
|
170
|
-
</div>
|
|
171
|
-
<div className="flex justify-between text-xs text-muted-foreground mt-1">
|
|
172
|
-
<span>{currentValue} {data.unit}</span>
|
|
173
|
-
<span>Target: {data.target} {data.unit}</span>
|
|
174
|
-
</div>
|
|
175
|
-
</motion.div>
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Değer formatı
|
|
180
|
-
const formatValue = (value: string | number): string => {
|
|
181
|
-
if (!isMounted) {
|
|
182
|
-
// Server-side: basit format kullan
|
|
183
|
-
if (typeof value === 'number') {
|
|
184
|
-
if (value >= 1000000) {
|
|
185
|
-
return (value / 1000000).toFixed(1) + 'M'
|
|
186
|
-
} else if (value >= 1000) {
|
|
187
|
-
return (value / 1000).toFixed(1) + 'K'
|
|
188
|
-
}
|
|
189
|
-
return value.toString()
|
|
190
|
-
}
|
|
191
|
-
return value.toString()
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Client-side: locale formatting kullanabilir
|
|
195
|
-
if (typeof value === 'number') {
|
|
196
|
-
if (value >= 1000000) {
|
|
197
|
-
return (value / 1000000).toFixed(1) + 'M'
|
|
198
|
-
} else if (value >= 1000) {
|
|
199
|
-
return (value / 1000).toFixed(1) + 'K'
|
|
200
|
-
}
|
|
201
|
-
// Nokta kullan, virgül değil
|
|
202
|
-
return value.toFixed(2)
|
|
203
|
-
}
|
|
204
|
-
return value.toString()
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Card variants
|
|
208
|
-
const cardVariants = {
|
|
209
|
-
initial: { opacity: 0, y: 20 },
|
|
210
|
-
animate: {
|
|
211
|
-
opacity: 1,
|
|
212
|
-
y: 0,
|
|
213
|
-
transition: { duration: 0.3, ease: "easeOut" }
|
|
214
|
-
},
|
|
215
|
-
hover: interactive ? {
|
|
216
|
-
y: -2,
|
|
217
|
-
transition: { duration: 0.2 }
|
|
218
|
-
} : {}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<motion.div
|
|
223
|
-
variants={cardVariants}
|
|
224
|
-
initial="initial"
|
|
225
|
-
animate="animate"
|
|
226
|
-
whileHover="hover"
|
|
227
|
-
onHoverStart={() => setIsHovered(true)}
|
|
228
|
-
onHoverEnd={() => setIsHovered(false)}
|
|
229
|
-
>
|
|
230
|
-
<Card
|
|
231
|
-
className={cn(
|
|
232
|
-
"relative overflow-hidden cursor-pointer transition-all duration-300",
|
|
233
|
-
interactive && "hover:shadow-lg hover:shadow-primary/5",
|
|
234
|
-
glassmorphism && "bg-background/60 backdrop-blur-md border-white/10",
|
|
235
|
-
className
|
|
236
|
-
)}
|
|
237
|
-
onClick={onClick}
|
|
238
|
-
>
|
|
239
|
-
{/* Açık arka plan efekti */}
|
|
240
|
-
{glassmorphism && (
|
|
241
|
-
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent" />
|
|
242
|
-
)}
|
|
243
|
-
|
|
244
|
-
{/* Üst bar */}
|
|
245
|
-
<div className="flex items-center justify-between p-4 pb-2">
|
|
246
|
-
<div className="flex items-center gap-3">
|
|
247
|
-
<motion.div
|
|
248
|
-
className={cn(
|
|
249
|
-
"p-2 rounded-lg",
|
|
250
|
-
glassmorphism ? "bg-white/10 backdrop-blur" : "bg-muted"
|
|
251
|
-
)}
|
|
252
|
-
whileHover={{ scale: 1.05 }}
|
|
253
|
-
whileTap={{ scale: 0.95 }}
|
|
254
|
-
>
|
|
255
|
-
<div className={cn("h-5 w-5", colorClasses[data.color || 'primary'])}>
|
|
256
|
-
{data.icon}
|
|
257
|
-
</div>
|
|
258
|
-
</motion.div>
|
|
259
|
-
<div>
|
|
260
|
-
<h3 className="text-sm font-medium text-muted-foreground">{data.title}</h3>
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
|
|
264
|
-
{/* Aksiyonlar */}
|
|
265
|
-
{interactive && (
|
|
266
|
-
<DropdownMenu>
|
|
267
|
-
<DropdownMenuTrigger asChild>
|
|
268
|
-
<Button
|
|
269
|
-
variant="ghost"
|
|
270
|
-
size="sm"
|
|
271
|
-
className="h-8 w-8 p-0"
|
|
272
|
-
onClick={(e) => e.stopPropagation()}
|
|
273
|
-
>
|
|
274
|
-
<MoreVertical className="h-4 w-4" />
|
|
275
|
-
</Button>
|
|
276
|
-
</DropdownMenuTrigger>
|
|
277
|
-
<DropdownMenuContent align="end">
|
|
278
|
-
<DropdownMenuItem onClick={() => onAction?.('fullscreen')}>
|
|
279
|
-
<Maximize2 className="mr-2 h-4 w-4" />
|
|
280
|
-
Fullscreen
|
|
281
|
-
</DropdownMenuItem>
|
|
282
|
-
<DropdownMenuItem onClick={() => onAction?.('export')}>
|
|
283
|
-
<Download className="mr-2 h-4 w-4" />
|
|
284
|
-
Export Data
|
|
285
|
-
</DropdownMenuItem>
|
|
286
|
-
<DropdownMenuItem onClick={() => onAction?.('share')}>
|
|
287
|
-
<Share2 className="mr-2 h-4 w-4" />
|
|
288
|
-
Share
|
|
289
|
-
</DropdownMenuItem>
|
|
290
|
-
</DropdownMenuContent>
|
|
291
|
-
</DropdownMenu>
|
|
292
|
-
)}
|
|
293
|
-
</div>
|
|
294
|
-
|
|
295
|
-
{/* Ana değer */}
|
|
296
|
-
<div className="px-4 pb-4">
|
|
297
|
-
<TooltipProvider>
|
|
298
|
-
<Tooltip>
|
|
299
|
-
<TooltipTrigger asChild>
|
|
300
|
-
<motion.div
|
|
301
|
-
className="text-3xl font-bold tracking-tight"
|
|
302
|
-
animate={{
|
|
303
|
-
scale: isHovered ? 1.02 : 1,
|
|
304
|
-
}}
|
|
305
|
-
transition={{ duration: 0.2 }}
|
|
306
|
-
>
|
|
307
|
-
{formatValue(data.value)}
|
|
308
|
-
{data.unit && <span className="text-lg ml-1 text-muted-foreground">{data.unit}</span>}
|
|
309
|
-
</motion.div>
|
|
310
|
-
</TooltipTrigger>
|
|
311
|
-
<TooltipContent>
|
|
312
|
-
<p>Exact value: {data.value} {data.unit}</p>
|
|
313
|
-
</TooltipContent>
|
|
314
|
-
</Tooltip>
|
|
315
|
-
</TooltipProvider>
|
|
316
|
-
|
|
317
|
-
{/* Değişim göstergesi */}
|
|
318
|
-
{data.change && (
|
|
319
|
-
<motion.div
|
|
320
|
-
className={cn(
|
|
321
|
-
"flex items-center gap-1 text-sm mt-2",
|
|
322
|
-
getChangeColor(data.change.type)
|
|
323
|
-
)}
|
|
324
|
-
initial={{ opacity: 0, x: -10 }}
|
|
325
|
-
animate={{ opacity: 1, x: 0 }}
|
|
326
|
-
transition={{ delay: 0.1 }}
|
|
327
|
-
>
|
|
328
|
-
{getChangeIcon(data.change.type)}
|
|
329
|
-
<span className="font-medium">
|
|
330
|
-
{data.change.type === 'neutral' ? '' : data.change.type === 'increase' ? '+' : '-'}
|
|
331
|
-
{Math.abs(data.change.value)}%
|
|
332
|
-
</span>
|
|
333
|
-
<span className="text-muted-foreground">
|
|
334
|
-
from {data.change.period}
|
|
335
|
-
</span>
|
|
336
|
-
</motion.div>
|
|
337
|
-
)}
|
|
338
|
-
|
|
339
|
-
{/* Tahmin */}
|
|
340
|
-
{showForecast && data.forecast && (
|
|
341
|
-
<motion.div
|
|
342
|
-
className="flex items-center gap-1 text-xs text-muted-foreground mt-1"
|
|
343
|
-
initial={{ opacity: 0 }}
|
|
344
|
-
animate={{ opacity: 1 }}
|
|
345
|
-
transition={{ delay: 0.2 }}
|
|
346
|
-
>
|
|
347
|
-
<TrendingUp className="h-3 w-3" />
|
|
348
|
-
<span>Forecast: {formatValue(data.forecast)} {data.unit}</span>
|
|
349
|
-
</motion.div>
|
|
350
|
-
)}
|
|
351
|
-
|
|
352
|
-
{/* İlerleme barı */}
|
|
353
|
-
{renderProgress()}
|
|
354
|
-
</div>
|
|
355
|
-
|
|
356
|
-
{/* Sparkline */}
|
|
357
|
-
{showSparkline && renderSparkline()}
|
|
358
|
-
</Card>
|
|
359
|
-
</motion.div>
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
export default MetricCard
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { motion } from 'framer-motion'
|
|
5
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card'
|
|
6
|
-
import { Progress } from '../../ui/progress'
|
|
7
|
-
import { cn } from '../../../lib/utils'
|
|
8
|
-
import { ProgressData } from '../types'
|
|
9
|
-
import { Clock, TrendingUp, Target } from 'lucide-react'
|
|
10
|
-
|
|
11
|
-
interface ProgressWidgetProps {
|
|
12
|
-
data: ProgressData | ProgressData[]
|
|
13
|
-
title?: string
|
|
14
|
-
className?: string
|
|
15
|
-
glassmorphism?: boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function ProgressWidget({
|
|
19
|
-
data,
|
|
20
|
-
title = "Progress",
|
|
21
|
-
className,
|
|
22
|
-
glassmorphism = false
|
|
23
|
-
}: ProgressWidgetProps) {
|
|
24
|
-
const items = Array.isArray(data) ? data : [data]
|
|
25
|
-
const [isMounted, setIsMounted] = React.useState(false)
|
|
26
|
-
|
|
27
|
-
React.useEffect(() => {
|
|
28
|
-
setIsMounted(true)
|
|
29
|
-
}, [])
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<Card className={cn(
|
|
33
|
-
"h-full flex flex-col",
|
|
34
|
-
glassmorphism && "bg-background/60 backdrop-blur-md border-white/10",
|
|
35
|
-
className
|
|
36
|
-
)}>
|
|
37
|
-
<CardHeader className="pb-3 space-y-0">
|
|
38
|
-
<CardTitle className="text-sm sm:text-base font-semibold flex items-center gap-2">
|
|
39
|
-
<Target className="h-4 w-4 text-muted-foreground" />
|
|
40
|
-
<span className="truncate">{title}</span>
|
|
41
|
-
</CardTitle>
|
|
42
|
-
</CardHeader>
|
|
43
|
-
<CardContent className="flex-1 space-y-3 overflow-auto">
|
|
44
|
-
{items.map((item, index) => {
|
|
45
|
-
const progress = (item.current / item.target) * 100
|
|
46
|
-
const isOverdue = item.deadline && new Date(item.deadline) < new Date()
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<motion.div
|
|
50
|
-
key={item.id}
|
|
51
|
-
initial={{ opacity: 0, y: 10 }}
|
|
52
|
-
animate={{ opacity: 1, y: 0 }}
|
|
53
|
-
transition={{ delay: index * 0.05 }}
|
|
54
|
-
className="space-y-2"
|
|
55
|
-
>
|
|
56
|
-
<div className="flex flex-col sm:flex-row sm:justify-between gap-1 sm:gap-2">
|
|
57
|
-
<div className="flex-1 min-w-0">
|
|
58
|
-
<h4 className="text-xs sm:text-sm font-medium truncate">{item.title}</h4>
|
|
59
|
-
{item.description && (
|
|
60
|
-
<p className="text-xs text-muted-foreground mt-0.5 truncate">{item.description}</p>
|
|
61
|
-
)}
|
|
62
|
-
</div>
|
|
63
|
-
<div className="text-left sm:text-right">
|
|
64
|
-
<p className="text-xs sm:text-sm font-semibold tabular-nums">
|
|
65
|
-
{item.current} / {item.target} {item.unit}
|
|
66
|
-
</p>
|
|
67
|
-
<p className="text-xs text-muted-foreground">
|
|
68
|
-
{progress.toFixed(1)}%
|
|
69
|
-
</p>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
<div className="relative">
|
|
74
|
-
<Progress
|
|
75
|
-
value={Math.min(progress, 100)}
|
|
76
|
-
className={cn(
|
|
77
|
-
"h-1.5 sm:h-2",
|
|
78
|
-
isOverdue && "[&>div]:bg-destructive"
|
|
79
|
-
)}
|
|
80
|
-
/>
|
|
81
|
-
{item.milestone && (
|
|
82
|
-
<div
|
|
83
|
-
className="absolute top-1/2 -translate-y-1/2 w-0.5 h-3 sm:h-4 bg-border"
|
|
84
|
-
style={{ left: `${(item.milestone / item.target) * 100}%` }}
|
|
85
|
-
/>
|
|
86
|
-
)}
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
|
-
{item.deadline && (
|
|
90
|
-
<div className={cn(
|
|
91
|
-
"flex items-center gap-1 text-xs",
|
|
92
|
-
isOverdue ? "text-destructive" : "text-muted-foreground"
|
|
93
|
-
)}>
|
|
94
|
-
<Clock className="h-3 w-3" />
|
|
95
|
-
<span>
|
|
96
|
-
{isOverdue ? 'Overdue' : 'Due'}: {isMounted ? new Date(item.deadline).toLocaleDateString() : 'Loading...'}
|
|
97
|
-
</span>
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
{item.trend && (
|
|
102
|
-
<div className="flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
|
|
103
|
-
<TrendingUp className="h-3 w-3" />
|
|
104
|
-
<span>+{item.trend}% this week</span>
|
|
105
|
-
</div>
|
|
106
|
-
)}
|
|
107
|
-
</motion.div>
|
|
108
|
-
)
|
|
109
|
-
})}
|
|
110
|
-
</CardContent>
|
|
111
|
-
</Card>
|
|
112
|
-
)
|
|
113
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { Loader2, MoreHorizontal, AlertTriangle } from 'lucide-react'
|
|
5
|
-
import {
|
|
6
|
-
DropdownMenu,
|
|
7
|
-
DropdownMenuContent,
|
|
8
|
-
DropdownMenuItem,
|
|
9
|
-
DropdownMenuSeparator,
|
|
10
|
-
DropdownMenuTrigger,
|
|
11
|
-
} from '../ui/dropdown-menu'
|
|
12
|
-
import {
|
|
13
|
-
AlertDialog,
|
|
14
|
-
AlertDialogAction,
|
|
15
|
-
AlertDialogCancel,
|
|
16
|
-
AlertDialogContent,
|
|
17
|
-
AlertDialogDescription,
|
|
18
|
-
AlertDialogFooter,
|
|
19
|
-
AlertDialogHeader,
|
|
20
|
-
AlertDialogTitle,
|
|
21
|
-
} from '../ui/alert-dialog'
|
|
22
|
-
import { Button } from '../ui/button'
|
|
23
|
-
import { Badge } from '../ui/badge'
|
|
24
|
-
import { cn } from '../../lib/utils'
|
|
25
|
-
|
|
26
|
-
export interface BulkAction<T = any> {
|
|
27
|
-
label: string
|
|
28
|
-
icon?: React.ReactNode
|
|
29
|
-
action: (selectedRows: T[]) => void | Promise<void>
|
|
30
|
-
confirmMessage?: string
|
|
31
|
-
confirmTitle?: string
|
|
32
|
-
variant?: 'default' | 'destructive'
|
|
33
|
-
disabled?: boolean | ((selectedRows: T[]) => boolean)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface DataTableBulkActionsProps<T = any> {
|
|
37
|
-
selectedRows: T[]
|
|
38
|
-
actions: BulkAction<T>[]
|
|
39
|
-
onClearSelection?: () => void
|
|
40
|
-
className?: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function DataTableBulkActions<T>({
|
|
44
|
-
selectedRows,
|
|
45
|
-
actions,
|
|
46
|
-
onClearSelection,
|
|
47
|
-
className
|
|
48
|
-
}: DataTableBulkActionsProps<T>) {
|
|
49
|
-
const [isLoading, setIsLoading] = React.useState(false)
|
|
50
|
-
const [pendingAction, setPendingAction] = React.useState<BulkAction<T> | null>(null)
|
|
51
|
-
|
|
52
|
-
const selectedCount = selectedRows.length
|
|
53
|
-
|
|
54
|
-
const handleAction = async (action: BulkAction<T>) => {
|
|
55
|
-
if (action.confirmMessage) {
|
|
56
|
-
setPendingAction(action)
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
await executeAction(action)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const executeAction = async (action: BulkAction<T>) => {
|
|
64
|
-
setIsLoading(true)
|
|
65
|
-
try {
|
|
66
|
-
await action.action(selectedRows)
|
|
67
|
-
|
|
68
|
-
// Clear selection after successful action
|
|
69
|
-
if (onClearSelection) {
|
|
70
|
-
onClearSelection()
|
|
71
|
-
}
|
|
72
|
-
} catch (error) {
|
|
73
|
-
console.error('Bulk action failed:', error)
|
|
74
|
-
// You might want to show an error toast here
|
|
75
|
-
} finally {
|
|
76
|
-
setIsLoading(false)
|
|
77
|
-
setPendingAction(null)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const handleConfirm = async () => {
|
|
82
|
-
if (pendingAction) {
|
|
83
|
-
await executeAction(pendingAction)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (selectedCount === 0) {
|
|
88
|
-
return null
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<>
|
|
93
|
-
<div className={cn("flex items-center gap-2", className)}>
|
|
94
|
-
{/* Selected count badge */}
|
|
95
|
-
<Badge variant="secondary" className="gap-1">
|
|
96
|
-
<span className="font-semibold">{selectedCount}</span>
|
|
97
|
-
<span>selected</span>
|
|
98
|
-
</Badge>
|
|
99
|
-
|
|
100
|
-
{/* Bulk actions dropdown */}
|
|
101
|
-
<DropdownMenu>
|
|
102
|
-
<DropdownMenuTrigger asChild>
|
|
103
|
-
<Button
|
|
104
|
-
variant="outline"
|
|
105
|
-
size="sm"
|
|
106
|
-
disabled={isLoading}
|
|
107
|
-
className="gap-2"
|
|
108
|
-
>
|
|
109
|
-
{isLoading ? (
|
|
110
|
-
<>
|
|
111
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
112
|
-
Processing...
|
|
113
|
-
</>
|
|
114
|
-
) : (
|
|
115
|
-
<>
|
|
116
|
-
<MoreHorizontal className="h-4 w-4" />
|
|
117
|
-
Bulk Actions
|
|
118
|
-
</>
|
|
119
|
-
)}
|
|
120
|
-
</Button>
|
|
121
|
-
</DropdownMenuTrigger>
|
|
122
|
-
<DropdownMenuContent align="end" className="w-48">
|
|
123
|
-
{actions.map((action, index) => {
|
|
124
|
-
const isDisabled = typeof action.disabled === 'function'
|
|
125
|
-
? action.disabled(selectedRows)
|
|
126
|
-
: action.disabled
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<DropdownMenuItem
|
|
130
|
-
key={index}
|
|
131
|
-
disabled={isDisabled || isLoading}
|
|
132
|
-
onSelect={() => handleAction(action)}
|
|
133
|
-
className={cn(
|
|
134
|
-
"cursor-pointer",
|
|
135
|
-
action.variant === 'destructive' && "text-destructive focus:text-destructive"
|
|
136
|
-
)}
|
|
137
|
-
>
|
|
138
|
-
{action.icon && (
|
|
139
|
-
<span className="mr-2 h-4 w-4">{action.icon}</span>
|
|
140
|
-
)}
|
|
141
|
-
{action.label}
|
|
142
|
-
</DropdownMenuItem>
|
|
143
|
-
)
|
|
144
|
-
})}
|
|
145
|
-
|
|
146
|
-
{onClearSelection && (
|
|
147
|
-
<>
|
|
148
|
-
<DropdownMenuSeparator />
|
|
149
|
-
<DropdownMenuItem
|
|
150
|
-
onSelect={onClearSelection}
|
|
151
|
-
className="cursor-pointer text-muted-foreground"
|
|
152
|
-
>
|
|
153
|
-
Clear selection
|
|
154
|
-
</DropdownMenuItem>
|
|
155
|
-
</>
|
|
156
|
-
)}
|
|
157
|
-
</DropdownMenuContent>
|
|
158
|
-
</DropdownMenu>
|
|
159
|
-
</div>
|
|
160
|
-
|
|
161
|
-
{/* Confirmation dialog */}
|
|
162
|
-
<AlertDialog
|
|
163
|
-
open={!!pendingAction}
|
|
164
|
-
onOpenChange={(open) => !open && setPendingAction(null)}
|
|
165
|
-
>
|
|
166
|
-
<AlertDialogContent>
|
|
167
|
-
<AlertDialogHeader>
|
|
168
|
-
<AlertDialogTitle className="flex items-center gap-2">
|
|
169
|
-
{pendingAction?.variant === 'destructive' && (
|
|
170
|
-
<AlertTriangle className="h-5 w-5 text-destructive" />
|
|
171
|
-
)}
|
|
172
|
-
{pendingAction?.confirmTitle || 'Confirm Action'}
|
|
173
|
-
</AlertDialogTitle>
|
|
174
|
-
<AlertDialogDescription>
|
|
175
|
-
{pendingAction?.confirmMessage ||
|
|
176
|
-
`This action will affect ${selectedCount} selected item${selectedCount > 1 ? 's' : ''}. This action cannot be undone.`}
|
|
177
|
-
</AlertDialogDescription>
|
|
178
|
-
</AlertDialogHeader>
|
|
179
|
-
<AlertDialogFooter>
|
|
180
|
-
<AlertDialogCancel disabled={isLoading}>
|
|
181
|
-
Cancel
|
|
182
|
-
</AlertDialogCancel>
|
|
183
|
-
<AlertDialogAction
|
|
184
|
-
onClick={handleConfirm}
|
|
185
|
-
disabled={isLoading}
|
|
186
|
-
className={cn(
|
|
187
|
-
pendingAction?.variant === 'destructive' && "bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
188
|
-
)}
|
|
189
|
-
>
|
|
190
|
-
{isLoading ? (
|
|
191
|
-
<>
|
|
192
|
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
193
|
-
Processing...
|
|
194
|
-
</>
|
|
195
|
-
) : (
|
|
196
|
-
'Confirm'
|
|
197
|
-
)}
|
|
198
|
-
</AlertDialogAction>
|
|
199
|
-
</AlertDialogFooter>
|
|
200
|
-
</AlertDialogContent>
|
|
201
|
-
</AlertDialog>
|
|
202
|
-
</>
|
|
203
|
-
)
|
|
204
|
-
}
|