@wakastellar/ui 2.4.0 → 3.1.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.
Files changed (173) hide show
  1. package/dist/blocks/antivirus-dashboard/index.d.ts +44 -0
  2. package/dist/blocks/clamav-service-status/index.d.ts +35 -0
  3. package/dist/blocks/file-scan-uploader/index.d.ts +29 -0
  4. package/dist/blocks/index.d.ts +18 -9
  5. package/dist/blocks/quarantine-manager/index.d.ts +27 -0
  6. package/dist/blocks/scan-history-log/index.d.ts +28 -0
  7. package/dist/blocks/scan-policy-editor/index.d.ts +27 -0
  8. package/dist/blocks/scan-report-generator/index.d.ts +47 -0
  9. package/dist/blocks/signature-database-manager/index.d.ts +39 -0
  10. package/dist/blocks/threat-alert-banner/index.d.ts +26 -0
  11. package/dist/components/index.d.ts +4 -4
  12. package/dist/components/waka-signature-pad/index.d.ts +1 -1
  13. package/dist/exceljs.min-BcLLX0PC.js +29 -0
  14. package/dist/exceljs.min-KOayaaQ4.mjs +23013 -0
  15. package/dist/export.cjs.js +1 -1
  16. package/dist/export.d.ts +2 -2
  17. package/dist/export.es.js +1 -1
  18. package/dist/index.cjs.js +136 -136
  19. package/dist/index.es.js +29978 -27215
  20. package/dist/stories/Button.stories.d.ts +1 -1
  21. package/dist/stories/Header.stories.d.ts +1 -1
  22. package/dist/stories/Page.stories.d.ts +1 -1
  23. package/dist/useDataTableImport-COVnvslz.js +9 -0
  24. package/dist/useDataTableImport-DAlxBY8w.mjs +237 -0
  25. package/dist/utils/index.d.ts +1 -0
  26. package/dist/utils/logger.d.ts +9 -0
  27. package/package.json +6 -5
  28. package/src/blocks/antivirus-dashboard/AntivirusDashboard.stories.tsx +291 -0
  29. package/src/blocks/antivirus-dashboard/index.tsx +525 -0
  30. package/src/blocks/clamav-service-status/ClamAVServiceStatus.stories.tsx +195 -0
  31. package/src/blocks/clamav-service-status/index.tsx +370 -0
  32. package/src/blocks/file-scan-uploader/FileScanUploader.stories.tsx +257 -0
  33. package/src/blocks/file-scan-uploader/index.tsx +311 -0
  34. package/src/blocks/index.ts +163 -11
  35. package/src/blocks/quarantine-manager/QuarantineManager.stories.tsx +209 -0
  36. package/src/blocks/quarantine-manager/index.tsx +435 -0
  37. package/src/blocks/scan-history-log/ScanHistoryLog.stories.tsx +231 -0
  38. package/src/blocks/scan-history-log/index.tsx +406 -0
  39. package/src/blocks/scan-policy-editor/ScanPolicyEditor.stories.tsx +106 -0
  40. package/src/blocks/scan-policy-editor/index.tsx +418 -0
  41. package/src/blocks/scan-report-generator/ScanReportGenerator.stories.tsx +232 -0
  42. package/src/blocks/scan-report-generator/index.tsx +612 -0
  43. package/src/blocks/sidebar/index.tsx +2 -1
  44. package/src/blocks/signature-database-manager/SignatureDatabaseManager.stories.tsx +279 -0
  45. package/src/blocks/signature-database-manager/index.tsx +470 -0
  46. package/src/blocks/theme-creator-block/index.tsx +16 -2
  47. package/src/blocks/threat-alert-banner/ThreatAlertBanner.stories.tsx +152 -0
  48. package/src/blocks/threat-alert-banner/index.tsx +320 -0
  49. package/src/components/DataTable/DataTable.stories.tsx +203 -0
  50. package/src/components/DataTable/hooks/useDataTableExport.ts +38 -31
  51. package/src/components/DataTable/hooks/useDataTableImport.ts +31 -20
  52. package/src/components/error-boundary/ErrorBoundary.stories.tsx +125 -0
  53. package/src/components/index.ts +45 -4
  54. package/src/components/language-selector/LanguageSelector.stories.tsx +112 -0
  55. package/src/components/theme-selector/ThemeSelector.stories.tsx +77 -0
  56. package/src/components/toaster/Toaster.stories.tsx +67 -0
  57. package/src/components/waka-activity-feed/WakaActivityFeed.stories.tsx +116 -0
  58. package/src/components/waka-ad-banner/WakaAdBanner.stories.tsx +102 -0
  59. package/src/components/waka-ad-fallback/WakaAdFallback.stories.tsx +117 -0
  60. package/src/components/waka-ad-inline/WakaAdInline.stories.tsx +105 -0
  61. package/src/components/waka-ad-interstitial/WakaAdInterstitial.stories.tsx +92 -0
  62. package/src/components/waka-ad-placeholder/WakaAdPlaceholder.stories.tsx +89 -0
  63. package/src/components/waka-ad-provider/WakaAdProvider.stories.tsx +110 -0
  64. package/src/components/waka-ad-sidebar/WakaAdSidebar.stories.tsx +89 -0
  65. package/src/components/waka-ad-sidebar/index.tsx +3 -2
  66. package/src/components/waka-ad-sticky-footer/WakaAdStickyFooter.stories.tsx +88 -0
  67. package/src/components/waka-address-autocomplete/WakaAddressAutocomplete.stories.tsx +46 -0
  68. package/src/components/waka-admincrumb/WakaAdmincrumb.stories.tsx +166 -0
  69. package/src/components/waka-alert-panel/WakaAlertPanel.stories.tsx +45 -0
  70. package/src/components/waka-alert-stack/WakaAlertStack.stories.tsx +62 -0
  71. package/src/components/waka-allocation-matrix/WakaAllocationMatrix.stories.tsx +68 -0
  72. package/src/components/waka-approval-chain/WakaApprovalChain.stories.tsx +63 -0
  73. package/src/components/waka-audit-log/WakaAuditLog.stories.tsx +73 -0
  74. package/src/components/waka-autocomplete/WakaAutocomplete.stories.tsx +132 -172
  75. package/src/components/waka-biometric-prompt/WakaBiometricPrompt.stories.tsx +48 -0
  76. package/src/components/waka-breadcrumb/WakaBreadcrumb.stories.tsx +74 -191
  77. package/src/components/waka-breadcrumb-path/WakaBreadcrumbPath.stories.tsx +40 -0
  78. package/src/components/waka-budget-burn/WakaBudgetBurn.stories.tsx +86 -0
  79. package/src/components/waka-capacity-planner/WakaCapacityPlanner.stories.tsx +273 -0
  80. package/src/components/waka-cart-summary/WakaCartSummary.stories.tsx +176 -0
  81. package/src/components/waka-cart-summary/index.tsx +19 -10
  82. package/src/components/waka-challenge-timer/WakaChallengeTimer.stories.tsx +98 -0
  83. package/src/components/waka-chat-bubble/WakaChatBubble.stories.tsx +118 -0
  84. package/src/components/waka-checklist/WakaChecklist.stories.tsx +71 -0
  85. package/src/components/waka-checkout-stepper/WakaCheckoutStepper.stories.tsx +102 -0
  86. package/src/components/waka-cohort-table/WakaCohortTable.stories.tsx +56 -0
  87. package/src/components/waka-color-picker/WakaColorPicker.stories.tsx +99 -155
  88. package/src/components/waka-combo-counter/WakaComboCounter.stories.tsx +128 -0
  89. package/src/components/waka-command-bar/WakaCommandBar.stories.tsx +45 -0
  90. package/src/components/waka-compare-period/WakaComparePeriod.stories.tsx +76 -0
  91. package/src/components/waka-config-comparator/WakaConfigComparator.stories.tsx +143 -0
  92. package/src/components/waka-connection-matrix/WakaConnectionMatrix.stories.tsx +52 -0
  93. package/src/components/waka-content-recommendation/WakaContentRecommendation.stories.tsx +41 -0
  94. package/src/components/waka-coupon-input/WakaCouponInput.stories.tsx +126 -0
  95. package/src/components/waka-credit-card-input/WakaCreditCardInput.stories.tsx +120 -0
  96. package/src/components/waka-datetime-picker.form-integration/WakaDateTimePickerForm.stories.tsx +79 -0
  97. package/src/components/waka-dependency-tree/WakaDependencyTree.stories.tsx +72 -0
  98. package/src/components/waka-device-trust/WakaDeviceTrust.stories.tsx +109 -0
  99. package/src/components/waka-empty-state/WakaEmptyState.stories.tsx +87 -0
  100. package/src/components/waka-feature-announcement/WakaFeatureAnnouncement.stories.tsx +47 -0
  101. package/src/components/waka-feature-flag-row/WakaFeatureFlagRow.stories.tsx +188 -0
  102. package/src/components/waka-file-upload/WakaFileUpload.stories.tsx +118 -174
  103. package/src/components/waka-floating-nav/WakaFloatingNav.stories.tsx +53 -0
  104. package/src/components/waka-goal-progress/WakaGoalProgress.stories.tsx +137 -0
  105. package/src/components/waka-hotspot/WakaHotspot.stories.tsx +56 -0
  106. package/src/components/waka-invoice-preview/WakaInvoicePreview.stories.tsx +169 -0
  107. package/src/components/waka-kpi-dashboard/WakaKpiDashboard.stories.tsx +46 -0
  108. package/src/components/waka-level-progress/WakaLevelProgress.stories.tsx +94 -75
  109. package/src/components/waka-liquid-button/WakaLiquidButton.stories.tsx +45 -0
  110. package/src/components/waka-magic-link/WakaMagicLink.stories.tsx +61 -0
  111. package/src/components/waka-magnetic-button/WakaMagneticButton.stories.tsx +40 -0
  112. package/src/components/waka-mention-input/WakaMentionInput.stories.tsx +140 -0
  113. package/src/components/waka-milestone-road/WakaMilestoneRoad.stories.tsx +143 -0
  114. package/src/components/waka-orbital-menu/WakaOrbitalMenu.stories.tsx +54 -0
  115. package/src/components/waka-order-tracker/WakaOrderTracker.stories.tsx +163 -0
  116. package/src/components/waka-outstream-video/WakaOutstreamVideo.stories.tsx +94 -0
  117. package/src/components/waka-pagination/WakaPagination.stories.tsx +110 -280
  118. package/src/components/waka-password-strength/WakaPasswordStrength.stories.tsx +132 -268
  119. package/src/components/waka-payment-method-picker/WakaPaymentMethodPicker.stories.tsx +141 -0
  120. package/src/components/waka-permission-matrix/WakaPermissionMatrix.stories.tsx +124 -0
  121. package/src/components/waka-phone-input/WakaPhoneInput.stories.tsx +56 -0
  122. package/src/components/waka-points-popup/WakaPointsPopup.stories.tsx +96 -0
  123. package/src/components/waka-power-up/WakaPowerUp.stories.tsx +121 -0
  124. package/src/components/waka-presence-indicator/WakaPresenceIndicator.stories.tsx +49 -0
  125. package/src/components/waka-pricing-table/WakaPricingTable.stories.tsx +159 -0
  126. package/src/components/waka-product-card/WakaProductCard.stories.tsx +202 -0
  127. package/src/components/waka-progress-onboarding/WakaProgressOnboarding.stories.tsx +57 -0
  128. package/src/components/waka-pull-to-refresh/WakaPullToRefresh.stories.tsx +51 -0
  129. package/src/components/waka-rank-badge/WakaRankBadge.stories.tsx +108 -0
  130. package/src/components/waka-rating-input/WakaRatingInput.stories.tsx +51 -0
  131. package/src/components/waka-reaction-picker/WakaReactionPicker.stories.tsx +52 -0
  132. package/src/components/waka-region-map/WakaRegionMap.stories.tsx +181 -0
  133. package/src/components/waka-resource-pool/WakaResourcePool.stories.tsx +70 -0
  134. package/src/components/waka-rich-text-editor/WakaRichTextEditor.stories.tsx +108 -197
  135. package/src/components/waka-rollback-slider/WakaRollbackSlider.stories.tsx +41 -0
  136. package/src/components/waka-schedule-picker/WakaSchedulePicker.stories.tsx +64 -0
  137. package/src/components/waka-season-pass/WakaSeasonPass.stories.tsx +107 -0
  138. package/src/components/waka-security-scan-result/WakaSecurityScanResult.stories.tsx +146 -0
  139. package/src/components/waka-security-score/WakaSecurityScore.stories.tsx +63 -0
  140. package/src/components/waka-session-manager/WakaSessionManager.stories.tsx +68 -0
  141. package/src/components/waka-signature-pad/WakaSignaturePad.stories.tsx +159 -0
  142. package/src/components/waka-signature-pad/index.tsx +5 -3
  143. package/src/components/waka-sla-tracker/WakaSlaTracker.stories.tsx +65 -0
  144. package/src/components/waka-slider-range/WakaSliderRange.stories.tsx +66 -0
  145. package/src/components/waka-sponsored-badge/WakaSponsoredBadge.stories.tsx +60 -0
  146. package/src/components/waka-sponsored-card/WakaSponsoredCard.stories.tsx +64 -0
  147. package/src/components/waka-sponsored-feed/WakaSponsoredFeed.stories.tsx +58 -0
  148. package/src/components/waka-spotlight/WakaSpotlight.stories.tsx +53 -0
  149. package/src/components/waka-stats-hexagon/WakaStatsHexagon.stories.tsx +161 -0
  150. package/src/components/waka-stepper/WakaStepper.stories.tsx +137 -410
  151. package/src/components/waka-swipe-card/WakaSwipeCard.stories.tsx +51 -0
  152. package/src/components/waka-tag-input/WakaTagInput.stories.tsx +224 -0
  153. package/src/components/waka-team-banner/WakaTeamBanner.stories.tsx +50 -0
  154. package/src/components/waka-theme-creator/WakaThemeCreator.stories.tsx +58 -0
  155. package/src/components/waka-theme-manager/WakaThemeManager.stories.tsx +298 -0
  156. package/src/components/waka-theme-manager/index.tsx +6 -11
  157. package/src/components/waka-thread-view/WakaThreadView.stories.tsx +143 -0
  158. package/src/components/waka-timeline/WakaTimeline.stories.tsx +171 -324
  159. package/src/components/waka-tooltip-tour/WakaTooltipTour.stories.tsx +92 -0
  160. package/src/components/waka-tour-guide/WakaTourGuide.stories.tsx +89 -0
  161. package/src/components/waka-treemap-chart/WakaTreemapChart.stories.tsx +234 -129
  162. package/src/components/waka-treemap-chart/index.tsx +2 -2
  163. package/src/components/waka-two-factor-setup/WakaTwoFactorSetup.stories.tsx +142 -0
  164. package/src/components/waka-typing-indicator/WakaTypingIndicator.stories.tsx +134 -0
  165. package/src/components/waka-video-ad/WakaVideoAd.stories.tsx +138 -0
  166. package/src/components/waka-video-call/WakaVideoCall.stories.tsx +186 -0
  167. package/src/components/waka-video-overlay/WakaVideoOverlay.stories.tsx +100 -0
  168. package/src/components/waka-voice-message/WakaVoiceMessage.stories.tsx +190 -0
  169. package/src/components/waka-welcome-modal/WakaWelcomeModal.stories.tsx +87 -0
  170. package/src/components/waka-xp-bar/WakaXPBar.stories.tsx +29 -29
  171. package/dist/useDataTableImport-D8R2HQl6.mjs +0 -229
  172. package/dist/useDataTableImport-S_hhA5Wo.js +0 -9
  173. package/src/components/DataTable/README.md +0 -446
@@ -0,0 +1,320 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import { Badge } from "../../components/badge"
5
+ import { Button } from "../../components/button"
6
+ import { Card } from "../../components/card"
7
+ import { ShieldAlert, AlertTriangle, AlertCircle, Info, X, ChevronDown, ChevronUp, Eye, Archive } from "lucide-react"
8
+ import { cn } from "../../utils/cn"
9
+
10
+ export type AlertSeverity = "critical" | "high" | "medium" | "low"
11
+ export type ThreatAction = "quarantined" | "deleted" | "blocked" | "allowed"
12
+
13
+ export interface ThreatAlert {
14
+ id: string
15
+ threatName: string
16
+ filename: string
17
+ filePath: string
18
+ severity: AlertSeverity
19
+ action: ThreatAction
20
+ detectedAt: Date
21
+ userId?: string
22
+ engineVersion: string
23
+ dismissed: boolean
24
+ }
25
+
26
+ export interface ThreatAlertBannerProps {
27
+ alerts?: ThreatAlert[]
28
+ maxVisible?: number
29
+ autoHideAfter?: number // ms, 0 = never
30
+ onDismiss?: (alertId: string) => void
31
+ onDismissAll?: () => void
32
+ onViewDetails?: (alertId: string) => void
33
+ onViewQuarantine?: () => void
34
+ className?: string
35
+ }
36
+
37
+ const severityConfig = {
38
+ critical: {
39
+ icon: ShieldAlert,
40
+ bgColor: "bg-red-950/50 border-red-800",
41
+ iconColor: "text-red-400",
42
+ badgeVariant: "destructive" as const,
43
+ },
44
+ high: {
45
+ icon: AlertTriangle,
46
+ bgColor: "bg-orange-950/50 border-orange-800",
47
+ iconColor: "text-orange-400",
48
+ badgeVariant: "destructive" as const,
49
+ },
50
+ medium: {
51
+ icon: AlertCircle,
52
+ bgColor: "bg-yellow-950/50 border-yellow-800",
53
+ iconColor: "text-yellow-400",
54
+ badgeVariant: "secondary" as const,
55
+ },
56
+ low: {
57
+ icon: Info,
58
+ bgColor: "bg-blue-950/50 border-blue-800",
59
+ iconColor: "text-blue-400",
60
+ badgeVariant: "default" as const,
61
+ },
62
+ }
63
+
64
+ const actionLabels: Record<ThreatAction, string> = {
65
+ quarantined: "Mis en quarantaine",
66
+ deleted: "Supprimé",
67
+ blocked: "Bloqué",
68
+ allowed: "Autorisé",
69
+ }
70
+
71
+ const actionColors: Record<ThreatAction, string> = {
72
+ quarantined: "text-orange-400",
73
+ deleted: "text-red-400",
74
+ blocked: "text-red-400",
75
+ allowed: "text-green-400",
76
+ }
77
+
78
+ export const ThreatAlertBanner = ({
79
+ alerts = [],
80
+ maxVisible = 3,
81
+ autoHideAfter = 0,
82
+ onDismiss,
83
+ onDismissAll,
84
+ onViewDetails,
85
+ onViewQuarantine,
86
+ className,
87
+ }: ThreatAlertBannerProps) => {
88
+ const [expandedAlertIds, setExpandedAlertIds] = useState<Set<string>>(new Set())
89
+
90
+ const activeAlerts = alerts.filter((alert) => !alert.dismissed)
91
+
92
+ if (activeAlerts.length === 0) {
93
+ return null
94
+ }
95
+
96
+ const toggleExpanded = (alertId: string) => {
97
+ setExpandedAlertIds((prev) => {
98
+ const next = new Set(prev)
99
+ if (next.has(alertId)) {
100
+ next.delete(alertId)
101
+ } else {
102
+ next.add(alertId)
103
+ }
104
+ return next
105
+ })
106
+ }
107
+
108
+ const visibleAlerts = activeAlerts.slice(0, maxVisible)
109
+ const remainingCount = activeAlerts.length - maxVisible
110
+
111
+ const formatTimestamp = (date: Date) => {
112
+ const now = new Date()
113
+ const diff = now.getTime() - date.getTime()
114
+ const minutes = Math.floor(diff / 60000)
115
+ const hours = Math.floor(diff / 3600000)
116
+
117
+ if (minutes < 1) return "À l'instant"
118
+ if (minutes < 60) return `Il y a ${minutes}m`
119
+ if (hours < 24) return `Il y a ${hours}h`
120
+ return date.toLocaleDateString("fr-FR", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" })
121
+ }
122
+
123
+ return (
124
+ <div className={cn("space-y-2", className)}>
125
+ {activeAlerts.length > 1 && (
126
+ <div className="flex items-center justify-between bg-red-950/30 border border-red-800/50 rounded-lg px-4 py-2">
127
+ <div className="flex items-center gap-2">
128
+ <ShieldAlert className="h-4 w-4 text-red-400" />
129
+ <span className="text-sm font-medium text-red-200">
130
+ {activeAlerts.length} alerte{activeAlerts.length > 1 ? "s" : ""} active{activeAlerts.length > 1 ? "s" : ""}
131
+ </span>
132
+ </div>
133
+ {onDismissAll && (
134
+ <Button variant="ghost" size="sm" onClick={onDismissAll} className="text-red-300 hover:text-red-200">
135
+ Tout ignorer
136
+ </Button>
137
+ )}
138
+ </div>
139
+ )}
140
+
141
+ {visibleAlerts.map((alert) => {
142
+ const config = severityConfig[alert.severity]
143
+ const Icon = config.icon
144
+ const isExpanded = expandedAlertIds.has(alert.id)
145
+
146
+ return (
147
+ <Card
148
+ key={alert.id}
149
+ className={cn(
150
+ "border-2 backdrop-blur-sm transition-all duration-200",
151
+ config.bgColor,
152
+ isExpanded && "shadow-lg"
153
+ )}
154
+ >
155
+ <div className="p-4">
156
+ <div className="flex items-start gap-3">
157
+ <div className={cn("mt-0.5 flex-shrink-0", config.iconColor)}>
158
+ <Icon className="h-5 w-5" />
159
+ </div>
160
+
161
+ <div className="flex-1 min-w-0">
162
+ <div className="flex items-start justify-between gap-3">
163
+ <div className="flex-1 min-w-0">
164
+ <div className="flex items-center gap-2 flex-wrap">
165
+ <h4 className="text-sm font-semibold text-white truncate">{alert.threatName}</h4>
166
+ <Badge variant={config.badgeVariant} className="text-xs">
167
+ {alert.severity.toUpperCase()}
168
+ </Badge>
169
+ </div>
170
+
171
+ <div className="mt-1 flex items-center gap-2 text-xs text-gray-400">
172
+ <span className="truncate" title={alert.filePath}>
173
+ {alert.filename}
174
+ </span>
175
+ <span>•</span>
176
+ <span className={actionColors[alert.action]}>{actionLabels[alert.action]}</span>
177
+ <span>•</span>
178
+ <span>{formatTimestamp(alert.detectedAt)}</span>
179
+ </div>
180
+ </div>
181
+
182
+ <Button
183
+ variant="ghost"
184
+ size="sm"
185
+ onClick={() => onDismiss?.(alert.id)}
186
+ className="flex-shrink-0 h-8 w-8 p-0 text-gray-400 hover:text-white"
187
+ >
188
+ <X className="h-4 w-4" />
189
+ </Button>
190
+ </div>
191
+
192
+ {isExpanded && (
193
+ <div className="mt-3 pt-3 border-t border-white/10 space-y-2 text-xs text-gray-300">
194
+ <div className="grid grid-cols-2 gap-2">
195
+ <div>
196
+ <span className="text-gray-500">Chemin complet:</span>
197
+ <p className="font-mono break-all">{alert.filePath}</p>
198
+ </div>
199
+ <div>
200
+ <span className="text-gray-500">Moteur antivirus:</span>
201
+ <p className="font-mono">{alert.engineVersion}</p>
202
+ </div>
203
+ {alert.userId && (
204
+ <div>
205
+ <span className="text-gray-500">Utilisateur:</span>
206
+ <p className="font-mono">{alert.userId}</p>
207
+ </div>
208
+ )}
209
+ <div>
210
+ <span className="text-gray-500">Détecté le:</span>
211
+ <p>
212
+ {alert.detectedAt.toLocaleString("fr-FR", {
213
+ dateStyle: "medium",
214
+ timeStyle: "short",
215
+ })}
216
+ </p>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ )}
221
+
222
+ <div className="mt-3 flex items-center gap-2">
223
+ <Button
224
+ variant="outline"
225
+ size="sm"
226
+ onClick={() => toggleExpanded(alert.id)}
227
+ className="text-xs"
228
+ >
229
+ {isExpanded ? (
230
+ <>
231
+ <ChevronUp className="h-3 w-3 mr-1" />
232
+ Masquer
233
+ </>
234
+ ) : (
235
+ <>
236
+ <ChevronDown className="h-3 w-3 mr-1" />
237
+ Détails
238
+ </>
239
+ )}
240
+ </Button>
241
+
242
+ {onViewDetails && (
243
+ <Button
244
+ variant="outline"
245
+ size="sm"
246
+ onClick={() => onViewDetails(alert.id)}
247
+ className="text-xs"
248
+ >
249
+ <Eye className="h-3 w-3 mr-1" />
250
+ Analyse complète
251
+ </Button>
252
+ )}
253
+
254
+ {alert.action === "quarantined" && onViewQuarantine && (
255
+ <Button
256
+ variant="outline"
257
+ size="sm"
258
+ onClick={onViewQuarantine}
259
+ className="text-xs"
260
+ >
261
+ <Archive className="h-3 w-3 mr-1" />
262
+ Quarantaine
263
+ </Button>
264
+ )}
265
+ </div>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </Card>
270
+ )
271
+ })}
272
+
273
+ {remainingCount > 0 && (
274
+ <div className="text-center">
275
+ <Button variant="ghost" size="sm" className="text-xs text-gray-400 hover:text-white">
276
+ + {remainingCount} autre{remainingCount > 1 ? "s" : ""} alerte{remainingCount > 1 ? "s" : ""}
277
+ </Button>
278
+ </div>
279
+ )}
280
+ </div>
281
+ )
282
+ }
283
+
284
+ export const defaultThreatAlerts: ThreatAlert[] = [
285
+ {
286
+ id: "1",
287
+ threatName: "Trojan.GenericKD.12345678",
288
+ filename: "invoice_2024.pdf.exe",
289
+ filePath: "/home/user/Downloads/invoice_2024.pdf.exe",
290
+ severity: "critical",
291
+ action: "quarantined",
292
+ detectedAt: new Date(Date.now() - 5 * 60000),
293
+ userId: "user@example.com",
294
+ engineVersion: "ClamAV 1.4.1",
295
+ dismissed: false,
296
+ },
297
+ {
298
+ id: "2",
299
+ threatName: "Malware.Suspicious.Archive",
300
+ filename: "files.zip",
301
+ filePath: "/var/www/uploads/files.zip",
302
+ severity: "high",
303
+ action: "blocked",
304
+ detectedAt: new Date(Date.now() - 15 * 60000),
305
+ engineVersion: "ClamAV 1.4.1",
306
+ dismissed: false,
307
+ },
308
+ {
309
+ id: "3",
310
+ threatName: "PUA.Win.Adware.Generic",
311
+ filename: "installer.exe",
312
+ filePath: "/tmp/installer.exe",
313
+ severity: "medium",
314
+ action: "deleted",
315
+ detectedAt: new Date(Date.now() - 3600000),
316
+ userId: "admin@example.com",
317
+ engineVersion: "ClamAV 1.4.1",
318
+ dismissed: false,
319
+ },
320
+ ]
@@ -0,0 +1,203 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import { DataTable } from "./index"
3
+ import { ColumnDef } from "@tanstack/react-table"
4
+
5
+ interface User {
6
+ id: string
7
+ name: string
8
+ email: string
9
+ role: string
10
+ status: "active" | "inactive" | "pending"
11
+ }
12
+
13
+ const sampleData: User[] = [
14
+ { id: "1", name: "John Doe", email: "john@example.com", role: "Admin", status: "active" },
15
+ { id: "2", name: "Jane Smith", email: "jane@example.com", role: "Editor", status: "active" },
16
+ { id: "3", name: "Bob Johnson", email: "bob@example.com", role: "Viewer", status: "inactive" },
17
+ { id: "4", name: "Alice Brown", email: "alice@example.com", role: "Editor", status: "pending" },
18
+ { id: "5", name: "Charlie Wilson", email: "charlie@example.com", role: "Admin", status: "active" },
19
+ { id: "6", name: "Diana Miller", email: "diana@example.com", role: "Viewer", status: "active" },
20
+ { id: "7", name: "Edward Davis", email: "edward@example.com", role: "Editor", status: "inactive" },
21
+ { id: "8", name: "Fiona Garcia", email: "fiona@example.com", role: "Admin", status: "active" },
22
+ ]
23
+
24
+ const columns: ColumnDef<User>[] = [
25
+ {
26
+ accessorKey: "name",
27
+ header: "Name",
28
+ },
29
+ {
30
+ accessorKey: "email",
31
+ header: "Email",
32
+ },
33
+ {
34
+ accessorKey: "role",
35
+ header: "Role",
36
+ },
37
+ {
38
+ accessorKey: "status",
39
+ header: "Status",
40
+ cell: ({ row }) => {
41
+ const status = row.getValue("status") as string
42
+ const colors = {
43
+ active: "bg-green-100 text-green-800",
44
+ inactive: "bg-gray-100 text-gray-800",
45
+ pending: "bg-yellow-100 text-yellow-800",
46
+ }
47
+ return (
48
+ <span className={`px-2 py-1 rounded-full text-xs font-medium ${colors[status as keyof typeof colors]}`}>
49
+ {status}
50
+ </span>
51
+ )
52
+ },
53
+ },
54
+ ]
55
+
56
+ const meta: Meta<typeof DataTable<User>> = {
57
+ title: "Components/Data/DataTable",
58
+ component: DataTable,
59
+ parameters: {
60
+ layout: "padded",
61
+ },
62
+ tags: ["autodocs"],
63
+ argTypes: {
64
+ layout: {
65
+ control: "select",
66
+ options: ["standard", "card", "compact", "split", "infinite"],
67
+ description: "Table layout style",
68
+ },
69
+ variant: {
70
+ control: "select",
71
+ options: ["bordered", "minimal", "striped", "glass"],
72
+ description: "Table visual variant",
73
+ },
74
+ density: {
75
+ control: "select",
76
+ options: ["compact", "comfortable", "spacious"],
77
+ description: "Row density",
78
+ },
79
+ selection: {
80
+ control: "boolean",
81
+ description: "Enable row selection",
82
+ },
83
+ loading: {
84
+ control: "boolean",
85
+ description: "Loading state",
86
+ },
87
+ headerSticky: {
88
+ control: "boolean",
89
+ description: "Sticky header",
90
+ },
91
+ columnVisibilityToggle: {
92
+ control: "boolean",
93
+ description: "Enable column visibility toggle",
94
+ },
95
+ enableExport: {
96
+ control: "boolean",
97
+ description: "Enable export functionality",
98
+ },
99
+ enableImport: {
100
+ control: "boolean",
101
+ description: "Enable import functionality",
102
+ },
103
+ },
104
+ }
105
+
106
+ export default meta
107
+ type Story = StoryObj<typeof DataTable<User>>
108
+
109
+ export const Default: Story = {
110
+ args: {
111
+ data: sampleData,
112
+ columns: columns,
113
+ layout: "standard",
114
+ variant: "bordered",
115
+ density: "comfortable",
116
+ },
117
+ render: (args) => <DataTable {...args} />,
118
+ }
119
+
120
+ export const WithSelection: Story = {
121
+ args: {
122
+ data: sampleData,
123
+ columns: columns,
124
+ selection: true,
125
+ variant: "bordered",
126
+ },
127
+ render: (args) => <DataTable {...args} />,
128
+ }
129
+
130
+ export const WithPagination: Story = {
131
+ args: {
132
+ data: sampleData,
133
+ columns: columns,
134
+ pagination: {
135
+ mode: "client",
136
+ pageSize: 5,
137
+ pageSizeOptions: [5, 10, 20],
138
+ },
139
+ variant: "bordered",
140
+ },
141
+ render: (args) => <DataTable {...args} />,
142
+ }
143
+
144
+ export const Striped: Story = {
145
+ args: {
146
+ data: sampleData,
147
+ columns: columns,
148
+ variant: "striped",
149
+ },
150
+ render: (args) => <DataTable {...args} />,
151
+ }
152
+
153
+ export const Compact: Story = {
154
+ args: {
155
+ data: sampleData,
156
+ columns: columns,
157
+ density: "compact",
158
+ variant: "bordered",
159
+ },
160
+ render: (args) => <DataTable {...args} />,
161
+ }
162
+
163
+ export const Loading: Story = {
164
+ args: {
165
+ data: [],
166
+ columns: columns,
167
+ loading: true,
168
+ },
169
+ render: (args) => <DataTable {...args} />,
170
+ }
171
+
172
+ export const Empty: Story = {
173
+ args: {
174
+ data: [],
175
+ columns: columns,
176
+ },
177
+ render: (args) => <DataTable {...args} />,
178
+ }
179
+
180
+ export const WithError: Story = {
181
+ args: {
182
+ data: [],
183
+ columns: columns,
184
+ error: "Failed to load data. Please try again.",
185
+ },
186
+ render: (args) => <DataTable {...args} />,
187
+ }
188
+
189
+ export const WithToolbar: Story = {
190
+ args: {
191
+ data: sampleData,
192
+ columns: columns,
193
+ toolbar: {
194
+ showGlobalSearch: true,
195
+ showColumnVisibility: true,
196
+ showDensityToggle: true,
197
+ },
198
+ enableExport: true,
199
+ variant: "bordered",
200
+ },
201
+ render: (args) => <DataTable {...args} />,
202
+ }
203
+
@@ -66,44 +66,47 @@ export function useDataTableExport<TData>({
66
66
  URL.revokeObjectURL(link.href)
67
67
  }, [config.filename])
68
68
 
69
- // Fonction d'export XLSX (nécessite une librairie externe)
69
+ // Fonction d'export XLSX (nécessite exceljs)
70
70
  const exportToXLSX = useCallback(async (data: TData[], filename?: string) => {
71
71
  try {
72
- // Vérifier si la librairie xlsx est disponible
73
72
  if (typeof window === 'undefined') {
74
73
  throw new Error("XLSX export not available in server environment")
75
74
  }
76
-
77
- // Import dynamique pour éviter d'augmenter la taille du bundle
75
+
78
76
  // @ts-ignore - Module importé dynamiquement
79
- const XLSX = await import("xlsx")
80
-
77
+ const ExcelJS = await import("exceljs")
78
+
79
+ const workbook = new ExcelJS.Workbook()
80
+ const worksheet = workbook.addWorksheet(config.options?.xlsx?.sheetName || "Sheet1")
81
+
81
82
  const headers = columns
82
83
  .filter(col => col.accessorKey || col.id)
83
84
  .map(col => col.header || col.accessorKey || col.id)
84
85
 
85
- const worksheetData = [
86
- headers,
87
- ...data.map(row =>
88
- columns
89
- .filter(col => col.accessorKey || col.id)
90
- .map(col => {
91
- const value = col.accessorKey ? (row as any)[col.accessorKey] : (row as any)[col.id]
92
- return col.formatter ? col.formatter(value) : value
93
- })
94
- )
95
- ]
96
-
97
- const worksheet = XLSX.utils.aoa_to_sheet(worksheetData)
98
- const workbook = XLSX.utils.book_new()
99
- XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1")
100
-
101
- XLSX.writeFile(workbook, `${filename || config.filename}.xlsx`)
86
+ worksheet.addRow(headers)
87
+
88
+ data.forEach(row => {
89
+ const values = columns
90
+ .filter(col => col.accessorKey || col.id)
91
+ .map(col => {
92
+ const value = col.accessorKey ? (row as any)[col.accessorKey] : (row as any)[col.id]
93
+ return col.formatter ? col.formatter(value) : value
94
+ })
95
+ worksheet.addRow(values)
96
+ })
97
+
98
+ const buffer = await workbook.xlsx.writeBuffer()
99
+ const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" })
100
+ const link = document.createElement("a")
101
+ link.href = URL.createObjectURL(blob)
102
+ link.download = `${filename || config.filename}.xlsx`
103
+ link.click()
104
+ URL.revokeObjectURL(link.href)
102
105
  } catch (error) {
103
106
  console.error("Erreur lors de l'export XLSX:", error)
104
- throw new Error("La librairie xlsx n'est pas disponible")
107
+ throw new Error("La librairie exceljs n'est pas disponible. Installez-la avec: pnpm add exceljs")
105
108
  }
106
- }, [columns, config.filename])
109
+ }, [columns, config.filename, config.options?.xlsx?.sheetName])
107
110
 
108
111
  // Fonction d'export PDF (nécessite une librairie externe)
109
112
  const exportToPDF = useCallback(async (data: TData[], filename?: string) => {
@@ -113,12 +116,15 @@ export function useDataTableExport<TData>({
113
116
  throw new Error("PDF export not available in server environment")
114
117
  }
115
118
 
116
- // @ts-ignore - Module importé dynamiquement
117
- const jsPDF = await import("jspdf")
118
- // @ts-ignore - Module importé dynamiquement
119
- const autoTable = await import("jspdf-autotable")
119
+ // Import dynamique des modules PDF
120
+ const jsPDFModule = await import("jspdf")
121
+ const autoTableModule = await import("jspdf-autotable")
122
+
123
+ const jsPDF = jsPDFModule.default || jsPDFModule.jsPDF
124
+ const doc = new jsPDF()
120
125
 
121
- const doc = new jsPDF.default()
126
+ // Appliquer le plugin autoTable au document
127
+ const autoTable = autoTableModule.default || autoTableModule.autoTable
122
128
 
123
129
  const headers = columns
124
130
  .filter(col => col.accessorKey || col.id)
@@ -133,7 +139,8 @@ export function useDataTableExport<TData>({
133
139
  })
134
140
  )
135
141
 
136
- doc.autoTable({
142
+ // Appeler autoTable comme fonction avec le document
143
+ autoTable(doc, {
137
144
  head: [headers],
138
145
  body: rows,
139
146
  startY: 20,
@@ -62,36 +62,47 @@ export function useDataTableImport<TData>({
62
62
  }
63
63
  }, [])
64
64
 
65
- // Fonction de parsing XLSX
65
+ // Fonction de parsing XLSX (nécessite exceljs)
66
66
  const parseXLSX = useCallback(async (file: File) => {
67
67
  try {
68
- // Vérifier si on est côté client
69
68
  if (typeof window === 'undefined') {
70
69
  throw new Error("XLSX parsing not available in server environment")
71
70
  }
72
-
71
+
73
72
  // @ts-ignore - Module importé dynamiquement
74
- const XLSX = await import("xlsx")
75
- const data = await file.arrayBuffer()
76
- const workbook = XLSX.read(data, { type: "array" })
77
- const sheetName = workbook.SheetNames[0]
78
- const worksheet = workbook.Sheets[sheetName]
79
- const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
80
-
81
- if (jsonData.length === 0) return []
82
-
83
- const headers = jsonData[0] as string[]
84
- return jsonData.slice(1).map((row: any[]) => {
85
- const obj: any = {}
73
+ const ExcelJS = await import("exceljs")
74
+ const buffer = await file.arrayBuffer()
75
+
76
+ const workbook = new ExcelJS.Workbook()
77
+ await workbook.xlsx.load(buffer)
78
+
79
+ const sheetIndex = config.parseOptions?.xlsx?.sheetIndex ?? 0
80
+ const headerRow = config.parseOptions?.xlsx?.headerRow ?? 1
81
+ const worksheet = workbook.worksheets[sheetIndex]
82
+
83
+ if (!worksheet || worksheet.rowCount === 0) return []
84
+
85
+ const headers: string[] = []
86
+ worksheet.getRow(headerRow).eachCell((cell, colNumber) => {
87
+ headers[colNumber - 1] = String(cell.value || "")
88
+ })
89
+
90
+ const data: Record<string, unknown>[] = []
91
+ for (let i = headerRow + 1; i <= worksheet.rowCount; i++) {
92
+ const row = worksheet.getRow(i)
93
+ const obj: Record<string, unknown> = {}
86
94
  headers.forEach((header, index) => {
87
- obj[header] = row[index] || ""
95
+ const cell = row.getCell(index + 1)
96
+ obj[header] = cell.value ?? ""
88
97
  })
89
- return obj
90
- })
98
+ data.push(obj)
99
+ }
100
+
101
+ return data
91
102
  } catch (error) {
92
- throw new Error("Erreur lors du parsing du fichier XLSX")
103
+ throw new Error("Erreur lors du parsing du fichier XLSX. Verifiez que exceljs est installe: pnpm add exceljs")
93
104
  }
94
- }, [])
105
+ }, [config.parseOptions?.xlsx?.sheetIndex, config.parseOptions?.xlsx?.headerRow])
95
106
 
96
107
  // Fonction de validation des données
97
108
  const validateData = useCallback((data: any[]) => {