@moontra/moonui-pro 2.20.2 → 2.20.4

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.
Files changed (153) hide show
  1. package/package.json +8 -3
  2. package/plugin/index.d.ts +86 -0
  3. package/plugin/index.js +308 -0
  4. package/scripts/postinstall.js +191 -23
  5. package/src/components/advanced-chart/index.tsx +0 -1246
  6. package/src/components/advanced-forms/index.tsx +0 -585
  7. package/src/components/animated-button/index.tsx +0 -385
  8. package/src/components/calendar/event-dialog.tsx +0 -377
  9. package/src/components/calendar/index.tsx +0 -1220
  10. package/src/components/calendar-pro/index.tsx +0 -1697
  11. package/src/components/color-picker/index.tsx +0 -432
  12. package/src/components/credit-card-input/index.tsx +0 -406
  13. package/src/components/dashboard/dashboard-grid.tsx +0 -480
  14. package/src/components/dashboard/demo.tsx +0 -425
  15. package/src/components/dashboard/index.tsx +0 -1046
  16. package/src/components/dashboard/time-range-picker.tsx +0 -336
  17. package/src/components/dashboard/types.ts +0 -225
  18. package/src/components/dashboard/widgets/activity-feed.tsx +0 -349
  19. package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
  20. package/src/components/dashboard/widgets/comparison-widget.tsx +0 -177
  21. package/src/components/dashboard/widgets/index.ts +0 -5
  22. package/src/components/dashboard/widgets/metric-card.tsx +0 -363
  23. package/src/components/dashboard/widgets/progress-widget.tsx +0 -113
  24. package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
  25. package/src/components/data-table/data-table-column-toggle.tsx +0 -169
  26. package/src/components/data-table/data-table-export.ts +0 -156
  27. package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
  28. package/src/components/data-table/index.tsx +0 -845
  29. package/src/components/draggable-list/index.tsx +0 -100
  30. package/src/components/error-boundary/index.tsx +0 -232
  31. package/src/components/file-upload/index.tsx +0 -1660
  32. package/src/components/floating-action-button/index.tsx +0 -206
  33. package/src/components/form-wizard/form-wizard-context.tsx +0 -335
  34. package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
  35. package/src/components/form-wizard/form-wizard-progress.tsx +0 -329
  36. package/src/components/form-wizard/form-wizard-step.tsx +0 -111
  37. package/src/components/form-wizard/index.tsx +0 -102
  38. package/src/components/form-wizard/types.ts +0 -77
  39. package/src/components/gesture-drawer/index.tsx +0 -551
  40. package/src/components/github-stars/github-api.ts +0 -426
  41. package/src/components/github-stars/hooks.ts +0 -517
  42. package/src/components/github-stars/index.tsx +0 -375
  43. package/src/components/github-stars/types.ts +0 -148
  44. package/src/components/github-stars/variants.tsx +0 -515
  45. package/src/components/health-check/index.tsx +0 -439
  46. package/src/components/hover-card-3d/index.tsx +0 -529
  47. package/src/components/index.ts +0 -130
  48. package/src/components/internal/index.ts +0 -78
  49. package/src/components/kanban/add-card-modal.tsx +0 -502
  50. package/src/components/kanban/card-detail-modal.tsx +0 -761
  51. package/src/components/kanban/index.ts +0 -13
  52. package/src/components/kanban/kanban.tsx +0 -1689
  53. package/src/components/kanban/types.ts +0 -168
  54. package/src/components/lazy-component/index.tsx +0 -823
  55. package/src/components/license-error/index.tsx +0 -31
  56. package/src/components/magnetic-button/index.tsx +0 -216
  57. package/src/components/memory-efficient-data/index.tsx +0 -1018
  58. package/src/components/moonui-quiz-form/index.tsx +0 -817
  59. package/src/components/navbar/index.tsx +0 -781
  60. package/src/components/optimized-image/index.tsx +0 -425
  61. package/src/components/performance-debugger/index.tsx +0 -613
  62. package/src/components/performance-monitor/index.tsx +0 -808
  63. package/src/components/phone-number-input/index.tsx +0 -343
  64. package/src/components/phone-number-input/phone-number-input-simple.tsx +0 -167
  65. package/src/components/pinch-zoom/index.tsx +0 -566
  66. package/src/components/quiz-form/index.tsx +0 -479
  67. package/src/components/rich-text-editor/index.tsx +0 -2322
  68. package/src/components/rich-text-editor/slash-commands-extension.ts +0 -230
  69. package/src/components/rich-text-editor/slash-commands.css +0 -35
  70. package/src/components/rich-text-editor/table-styles.css +0 -65
  71. package/src/components/sidebar/index.tsx +0 -884
  72. package/src/components/spotlight-card/index.tsx +0 -191
  73. package/src/components/swipeable-card/index.tsx +0 -100
  74. package/src/components/timeline/index.tsx +0 -1183
  75. package/src/components/ui/accordion.tsx +0 -581
  76. package/src/components/ui/alert-dialog.tsx +0 -141
  77. package/src/components/ui/alert.tsx +0 -141
  78. package/src/components/ui/aspect-ratio.tsx +0 -245
  79. package/src/components/ui/avatar.tsx +0 -155
  80. package/src/components/ui/badge.tsx +0 -230
  81. package/src/components/ui/breadcrumb.tsx +0 -216
  82. package/src/components/ui/button.tsx +0 -228
  83. package/src/components/ui/calendar.tsx +0 -387
  84. package/src/components/ui/card.tsx +0 -216
  85. package/src/components/ui/checkbox.tsx +0 -259
  86. package/src/components/ui/collapsible.tsx +0 -631
  87. package/src/components/ui/color-picker.tsx +0 -97
  88. package/src/components/ui/command.tsx +0 -948
  89. package/src/components/ui/dialog.tsx +0 -752
  90. package/src/components/ui/dropdown-menu.tsx +0 -706
  91. package/src/components/ui/gesture-drawer.tsx +0 -11
  92. package/src/components/ui/hover-card.tsx +0 -29
  93. package/src/components/ui/index.ts +0 -222
  94. package/src/components/ui/input.tsx +0 -224
  95. package/src/components/ui/label.tsx +0 -29
  96. package/src/components/ui/lightbox.tsx +0 -606
  97. package/src/components/ui/magnetic-button.tsx +0 -129
  98. package/src/components/ui/media-gallery.tsx +0 -611
  99. package/src/components/ui/navigation-menu.tsx +0 -130
  100. package/src/components/ui/pagination.tsx +0 -125
  101. package/src/components/ui/popover.tsx +0 -185
  102. package/src/components/ui/progress.tsx +0 -30
  103. package/src/components/ui/radio-group.tsx +0 -257
  104. package/src/components/ui/scroll-area.tsx +0 -47
  105. package/src/components/ui/select.tsx +0 -378
  106. package/src/components/ui/separator.tsx +0 -145
  107. package/src/components/ui/sheet.tsx +0 -139
  108. package/src/components/ui/skeleton.tsx +0 -20
  109. package/src/components/ui/slider.tsx +0 -354
  110. package/src/components/ui/spotlight-card.tsx +0 -119
  111. package/src/components/ui/switch.tsx +0 -86
  112. package/src/components/ui/table.tsx +0 -331
  113. package/src/components/ui/tabs-pro.tsx +0 -542
  114. package/src/components/ui/tabs.tsx +0 -54
  115. package/src/components/ui/textarea.tsx +0 -28
  116. package/src/components/ui/toast.tsx +0 -317
  117. package/src/components/ui/toggle.tsx +0 -119
  118. package/src/components/ui/tooltip.tsx +0 -151
  119. package/src/components/virtual-list/index.tsx +0 -668
  120. package/src/hooks/use-chart.ts +0 -205
  121. package/src/hooks/use-data-table.ts +0 -182
  122. package/src/hooks/use-docs-pro-access.ts +0 -13
  123. package/src/hooks/use-license-check.ts +0 -65
  124. package/src/hooks/use-subscription.ts +0 -19
  125. package/src/hooks/use-toast.ts +0 -15
  126. package/src/index.ts +0 -22
  127. package/src/lib/ai-providers.ts +0 -377
  128. package/src/lib/component-metadata.ts +0 -18
  129. package/src/lib/micro-interactions.ts +0 -255
  130. package/src/lib/paddle.ts +0 -17
  131. package/src/lib/utils.ts +0 -6
  132. package/src/patterns/login-form/index.tsx +0 -276
  133. package/src/patterns/login-form/types.ts +0 -67
  134. package/src/setupTests.ts +0 -41
  135. package/src/styles/advanced-chart.css +0 -239
  136. package/src/styles/calendar.css +0 -35
  137. package/src/styles/design-system.css +0 -363
  138. package/src/styles/index.css +0 -681
  139. package/src/styles/tailwind.css +0 -7
  140. package/src/styles/tokens.css +0 -455
  141. package/src/types/next-auth.d.ts +0 -21
  142. package/src/use-intersection-observer.tsx +0 -154
  143. package/src/use-local-storage.tsx +0 -71
  144. package/src/use-paddle.ts +0 -138
  145. package/src/use-performance-optimizer.ts +0 -389
  146. package/src/use-pro-access.ts +0 -141
  147. package/src/use-scroll-animation.ts +0 -219
  148. package/src/use-subscription.ts +0 -37
  149. package/src/use-toast.ts +0 -32
  150. package/src/utils/chart-helpers.ts +0 -357
  151. package/src/utils/cn.ts +0 -6
  152. package/src/utils/data-processing.ts +0 -151
  153. 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
- }