@wakastellar/ui 2.0.0 → 2.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 (290) hide show
  1. package/dist/cli/commands/add.d.ts +7 -0
  2. package/dist/cli/commands/init.d.ts +6 -0
  3. package/dist/cli/commands/list.d.ts +5 -0
  4. package/dist/cli/commands/search.d.ts +1 -0
  5. package/dist/cli/index.cjs +4844 -0
  6. package/dist/cli/index.d.ts +1 -0
  7. package/dist/cli/utils/config.d.ts +29 -0
  8. package/dist/cli/utils/logger.d.ts +20 -0
  9. package/dist/cli/utils/registry.d.ts +23 -0
  10. package/package.json +14 -3
  11. package/src/blocks/activity-timeline/index.tsx +586 -0
  12. package/src/blocks/calendar-view/index.tsx +756 -0
  13. package/src/blocks/chat/index.tsx +1018 -0
  14. package/src/blocks/chat/widget.tsx +504 -0
  15. package/src/blocks/dashboard/index.tsx +522 -0
  16. package/src/blocks/empty-states/index.tsx +452 -0
  17. package/src/blocks/error-pages/index.tsx +426 -0
  18. package/src/blocks/faq/index.tsx +479 -0
  19. package/src/blocks/file-manager/index.tsx +890 -0
  20. package/src/blocks/footer/index.tsx +133 -0
  21. package/src/blocks/header/index.tsx +357 -0
  22. package/src/blocks/headtab/index.tsx +139 -0
  23. package/src/blocks/i18n-editor/index.tsx +1016 -0
  24. package/src/blocks/index.ts +80 -0
  25. package/src/blocks/kanban-board/index.tsx +779 -0
  26. package/src/blocks/landing/index.tsx +677 -0
  27. package/src/blocks/language-selector/index.tsx +88 -0
  28. package/src/blocks/layout/index.tsx +159 -0
  29. package/src/blocks/login/index.tsx +339 -0
  30. package/src/blocks/login/types.ts +131 -0
  31. package/src/blocks/pricing/index.tsx +564 -0
  32. package/src/blocks/profile/index.tsx +746 -0
  33. package/src/blocks/settings/index.tsx +558 -0
  34. package/src/blocks/sidebar/index.tsx +713 -0
  35. package/src/blocks/theme-creator-block/index.tsx +835 -0
  36. package/src/blocks/user-management/index.tsx +1037 -0
  37. package/src/blocks/wizard/index.tsx +719 -0
  38. package/src/components/DataTable/DataTable.tsx +406 -0
  39. package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
  40. package/src/components/DataTable/DataTableBody.tsx +216 -0
  41. package/src/components/DataTable/DataTableCell.tsx +172 -0
  42. package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
  43. package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
  44. package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
  45. package/src/components/DataTable/DataTableEditCell.tsx +279 -0
  46. package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
  47. package/src/components/DataTable/DataTableFilters.tsx +535 -0
  48. package/src/components/DataTable/DataTableGrouping.tsx +147 -0
  49. package/src/components/DataTable/DataTableHeader.tsx +172 -0
  50. package/src/components/DataTable/DataTablePagination.tsx +125 -0
  51. package/src/components/DataTable/DataTableSelection.tsx +269 -0
  52. package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
  53. package/src/components/DataTable/DataTableToolbar.tsx +262 -0
  54. package/src/components/DataTable/README.md +446 -0
  55. package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
  56. package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
  57. package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
  58. package/src/components/DataTable/examples/EditExample.tsx +166 -0
  59. package/src/components/DataTable/formatters/index.ts +335 -0
  60. package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
  61. package/src/components/DataTable/hooks/useDataTable.ts +145 -0
  62. package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
  63. package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
  64. package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
  65. package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
  66. package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
  67. package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
  68. package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
  69. package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
  70. package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
  71. package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
  72. package/src/components/DataTable/index.ts +81 -0
  73. package/src/components/DataTable/services/IndexedDBService.ts +504 -0
  74. package/src/components/DataTable/templates/index.tsx +803 -0
  75. package/src/components/DataTable/types.ts +504 -0
  76. package/src/components/DataTable/utils.ts +164 -0
  77. package/src/components/DataTable/workers/exportWorker.ts +213 -0
  78. package/src/components/accordion/index.tsx +61 -0
  79. package/src/components/alert/index.tsx +61 -0
  80. package/src/components/alert-dialog/index.tsx +146 -0
  81. package/src/components/aspect-ratio/index.tsx +12 -0
  82. package/src/components/avatar/index.tsx +54 -0
  83. package/src/components/badge/Badge.stories.tsx +64 -0
  84. package/src/components/badge/index.tsx +38 -0
  85. package/src/components/button/Button.stories.tsx +173 -0
  86. package/src/components/button/index.tsx +56 -0
  87. package/src/components/calendar/index.tsx +73 -0
  88. package/src/components/card/index.tsx +78 -0
  89. package/src/components/checkbox/index.tsx +34 -0
  90. package/src/components/code/index.tsx +229 -0
  91. package/src/components/collapsible/index.tsx +16 -0
  92. package/src/components/command/index.tsx +162 -0
  93. package/src/components/context-menu/index.tsx +204 -0
  94. package/src/components/dialog/index.tsx +126 -0
  95. package/src/components/dropdown-menu/index.tsx +204 -0
  96. package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
  97. package/src/components/error-boundary/index.ts +7 -0
  98. package/src/components/form/index.tsx +183 -0
  99. package/src/components/hover-card/index.tsx +33 -0
  100. package/src/components/index.ts +368 -0
  101. package/src/components/input/Input.stories.tsx +100 -0
  102. package/src/components/input/index.tsx +27 -0
  103. package/src/components/input-otp/index.tsx +277 -0
  104. package/src/components/label/index.tsx +30 -0
  105. package/src/components/language-selector/index.tsx +341 -0
  106. package/src/components/menubar/index.tsx +240 -0
  107. package/src/components/navigation-menu/index.tsx +134 -0
  108. package/src/components/popover/index.tsx +35 -0
  109. package/src/components/progress/index.tsx +32 -0
  110. package/src/components/radio-group/index.tsx +48 -0
  111. package/src/components/scroll-area/index.tsx +52 -0
  112. package/src/components/select/index.tsx +164 -0
  113. package/src/components/separator/index.tsx +35 -0
  114. package/src/components/sheet/index.tsx +147 -0
  115. package/src/components/skeleton/index.tsx +22 -0
  116. package/src/components/slider/index.tsx +32 -0
  117. package/src/components/switch/index.tsx +33 -0
  118. package/src/components/table/index.tsx +117 -0
  119. package/src/components/tabs/index.tsx +59 -0
  120. package/src/components/textarea/index.tsx +30 -0
  121. package/src/components/theme-selector/index.tsx +327 -0
  122. package/src/components/toast/index.tsx +133 -0
  123. package/src/components/toaster/index.tsx +34 -0
  124. package/src/components/toggle/index.tsx +49 -0
  125. package/src/components/tooltip/index.tsx +34 -0
  126. package/src/components/typography/index.tsx +276 -0
  127. package/src/components/waka-3d-pie-chart/index.tsx +486 -0
  128. package/src/components/waka-achievement-unlock/index.tsx +716 -0
  129. package/src/components/waka-activity-feed/index.tsx +686 -0
  130. package/src/components/waka-address-autocomplete/index.tsx +1202 -0
  131. package/src/components/waka-admincrumb/index.tsx +349 -0
  132. package/src/components/waka-alert-stack/index.tsx +827 -0
  133. package/src/components/waka-allocation-matrix/index.tsx +1278 -0
  134. package/src/components/waka-approval-chain/index.tsx +766 -0
  135. package/src/components/waka-audit-log/index.tsx +1475 -0
  136. package/src/components/waka-autocomplete/index.tsx +358 -0
  137. package/src/components/waka-badge-showcase/index.tsx +704 -0
  138. package/src/components/waka-barcode/index.tsx +260 -0
  139. package/src/components/waka-biometric-prompt/index.tsx +765 -0
  140. package/src/components/waka-bottom-sheet/index.tsx +495 -0
  141. package/src/components/waka-breadcrumb/index.tsx +376 -0
  142. package/src/components/waka-breadcrumb-path/index.tsx +513 -0
  143. package/src/components/waka-budget-burn/index.tsx +1234 -0
  144. package/src/components/waka-capacity-planner/index.tsx +1107 -0
  145. package/src/components/waka-carousel/index.tsx +893 -0
  146. package/src/components/waka-cart-summary/index.tsx +1055 -0
  147. package/src/components/waka-challenge-timer/index.tsx +1044 -0
  148. package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
  149. package/src/components/waka-charts/WakaBarChart.tsx +222 -0
  150. package/src/components/waka-charts/WakaChart.tsx +124 -0
  151. package/src/components/waka-charts/WakaLineChart.tsx +219 -0
  152. package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
  153. package/src/components/waka-charts/WakaPieChart.tsx +214 -0
  154. package/src/components/waka-charts/WakaSparkline.tsx +229 -0
  155. package/src/components/waka-charts/dataTableHelpers.ts +109 -0
  156. package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
  157. package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
  158. package/src/components/waka-charts/index.ts +90 -0
  159. package/src/components/waka-charts/types.ts +330 -0
  160. package/src/components/waka-chat-bubble/index.tsx +1060 -0
  161. package/src/components/waka-checklist/index.tsx +1067 -0
  162. package/src/components/waka-checkout-stepper/index.tsx +976 -0
  163. package/src/components/waka-cohort-table/index.tsx +1011 -0
  164. package/src/components/waka-color-picker/index.tsx +447 -0
  165. package/src/components/waka-combo-counter/index.tsx +864 -0
  166. package/src/components/waka-combobox/index.tsx +497 -0
  167. package/src/components/waka-command-bar/index.tsx +403 -0
  168. package/src/components/waka-compare-period/index.tsx +1230 -0
  169. package/src/components/waka-connection-matrix/index.tsx +1053 -0
  170. package/src/components/waka-contribution-graph/index.tsx +552 -0
  171. package/src/components/waka-cost-breakdown/index.tsx +1065 -0
  172. package/src/components/waka-coupon-input/index.tsx +592 -0
  173. package/src/components/waka-credit-card-input/index.tsx +982 -0
  174. package/src/components/waka-daily-reward/index.tsx +762 -0
  175. package/src/components/waka-date-range-picker/index.tsx +378 -0
  176. package/src/components/waka-datetime-picker/index.tsx +793 -0
  177. package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
  178. package/src/components/waka-deployment-lane/index.tsx +673 -0
  179. package/src/components/waka-device-trust/index.tsx +1259 -0
  180. package/src/components/waka-dock/index.tsx +285 -0
  181. package/src/components/waka-drawer/index.tsx +319 -0
  182. package/src/components/waka-empty-state/index.tsx +545 -0
  183. package/src/components/waka-error-shake/index.tsx +398 -0
  184. package/src/components/waka-feature-announcement/index.tsx +991 -0
  185. package/src/components/waka-file-upload/index.tsx +437 -0
  186. package/src/components/waka-floating-nav/index.tsx +413 -0
  187. package/src/components/waka-flow-diagram/index.tsx +508 -0
  188. package/src/components/waka-funnel-chart/index.tsx +823 -0
  189. package/src/components/waka-glow-card/index.tsx +246 -0
  190. package/src/components/waka-goal-progress/index.tsx +1025 -0
  191. package/src/components/waka-haptic-button/index.tsx +388 -0
  192. package/src/components/waka-health-pulse/index.tsx +451 -0
  193. package/src/components/waka-heatmap/index.tsx +1026 -0
  194. package/src/components/waka-hotspot/index.tsx +682 -0
  195. package/src/components/waka-image/index.tsx +373 -0
  196. package/src/components/waka-incident-timeline/index.tsx +686 -0
  197. package/src/components/waka-invoice-preview/index.tsx +829 -0
  198. package/src/components/waka-kanban/index.tsx +646 -0
  199. package/src/components/waka-kpi-dashboard/index.tsx +755 -0
  200. package/src/components/waka-leaderboard/index.tsx +746 -0
  201. package/src/components/waka-level-progress/index.tsx +665 -0
  202. package/src/components/waka-liquid-button/index.tsx +520 -0
  203. package/src/components/waka-loading-orbit/index.tsx +478 -0
  204. package/src/components/waka-loot-box/index.tsx +1091 -0
  205. package/src/components/waka-magic-link/index.tsx +321 -0
  206. package/src/components/waka-magnetic-button/index.tsx +567 -0
  207. package/src/components/waka-mention-input/index.tsx +953 -0
  208. package/src/components/waka-metric-sparkline/index.tsx +627 -0
  209. package/src/components/waka-milestone-road/index.tsx +1064 -0
  210. package/src/components/waka-modal/index.tsx +374 -0
  211. package/src/components/waka-morph-button/index.tsx +495 -0
  212. package/src/components/waka-network-topology/index.tsx +801 -0
  213. package/src/components/waka-notifications/index.tsx +414 -0
  214. package/src/components/waka-number-input/index.tsx +373 -0
  215. package/src/components/waka-orbital-menu/index.tsx +445 -0
  216. package/src/components/waka-order-tracker/index.tsx +1041 -0
  217. package/src/components/waka-pagination/index.tsx +393 -0
  218. package/src/components/waka-password-strength/index.tsx +824 -0
  219. package/src/components/waka-payment-method-picker/index.tsx +715 -0
  220. package/src/components/waka-permission-matrix/index.tsx +1302 -0
  221. package/src/components/waka-phone-input/index.tsx +801 -0
  222. package/src/components/waka-pipeline-view/index.tsx +604 -0
  223. package/src/components/waka-player-card/index.tsx +691 -0
  224. package/src/components/waka-points-popup/index.tsx +366 -0
  225. package/src/components/waka-power-up/index.tsx +1155 -0
  226. package/src/components/waka-presence-indicator/index.tsx +1181 -0
  227. package/src/components/waka-pricing-table/index.tsx +755 -0
  228. package/src/components/waka-product-card/index.tsx +786 -0
  229. package/src/components/waka-progress-onboarding/index.tsx +878 -0
  230. package/src/components/waka-pull-to-refresh/index.tsx +451 -0
  231. package/src/components/waka-qrcode/index.tsx +232 -0
  232. package/src/components/waka-quest-card/index.tsx +1275 -0
  233. package/src/components/waka-quota-bar/index.tsx +693 -0
  234. package/src/components/waka-radar-score/index.tsx +512 -0
  235. package/src/components/waka-rank-badge/index.tsx +813 -0
  236. package/src/components/waka-rating-input/index.tsx +560 -0
  237. package/src/components/waka-reaction-picker/index.tsx +1062 -0
  238. package/src/components/waka-region-map/index.tsx +730 -0
  239. package/src/components/waka-resource-gauge/index.tsx +654 -0
  240. package/src/components/waka-resource-pool/index.tsx +1035 -0
  241. package/src/components/waka-rich-text-editor/index.tsx +594 -0
  242. package/src/components/waka-rollback-slider/index.tsx +891 -0
  243. package/src/components/waka-sankey-diagram/index.tsx +1032 -0
  244. package/src/components/waka-schedule-picker/index.tsx +1060 -0
  245. package/src/components/waka-scratch-card/index.tsx +914 -0
  246. package/src/components/waka-season-pass/index.tsx +886 -0
  247. package/src/components/waka-security-score/index.tsx +1126 -0
  248. package/src/components/waka-segmented-control/index.tsx +238 -0
  249. package/src/components/waka-server-rack/index.tsx +764 -0
  250. package/src/components/waka-session-manager/index.tsx +815 -0
  251. package/src/components/waka-signature-pad/index.tsx +744 -0
  252. package/src/components/waka-skeleton-wave/index.tsx +454 -0
  253. package/src/components/waka-skill-tree/index.tsx +1031 -0
  254. package/src/components/waka-sla-tracker/index.tsx +798 -0
  255. package/src/components/waka-slider-range/index.tsx +765 -0
  256. package/src/components/waka-spin-wheel/index.tsx +671 -0
  257. package/src/components/waka-spinner/index.tsx +284 -0
  258. package/src/components/waka-spotlight/index.tsx +410 -0
  259. package/src/components/waka-stat/index.tsx +428 -0
  260. package/src/components/waka-stats-hexagon/index.tsx +824 -0
  261. package/src/components/waka-status-matrix/index.tsx +565 -0
  262. package/src/components/waka-stepper/index.tsx +489 -0
  263. package/src/components/waka-streak-counter/index.tsx +334 -0
  264. package/src/components/waka-success-explosion/index.tsx +453 -0
  265. package/src/components/waka-swipe-card/index.tsx +574 -0
  266. package/src/components/waka-tabs-morph/index.tsx +509 -0
  267. package/src/components/waka-tag-input/index.tsx +877 -0
  268. package/src/components/waka-team-banner/index.tsx +1183 -0
  269. package/src/components/waka-terminal-output/index.tsx +836 -0
  270. package/src/components/waka-theme-creator/index.tsx +762 -0
  271. package/src/components/waka-theme-manager/index.tsx +654 -0
  272. package/src/components/waka-thread-view/index.tsx +874 -0
  273. package/src/components/waka-tilt-card/index.tsx +250 -0
  274. package/src/components/waka-time-picker/index.tsx +479 -0
  275. package/src/components/waka-timeline/index.tsx +385 -0
  276. package/src/components/waka-tooltip-tour/index.tsx +855 -0
  277. package/src/components/waka-tour-guide/index.tsx +920 -0
  278. package/src/components/waka-tournament-bracket/index.tsx +1276 -0
  279. package/src/components/waka-tree/index.tsx +557 -0
  280. package/src/components/waka-treemap-chart/index.tsx +1031 -0
  281. package/src/components/waka-two-factor-setup/index.tsx +995 -0
  282. package/src/components/waka-typewriter/index.tsx +566 -0
  283. package/src/components/waka-typing-indicator/index.tsx +649 -0
  284. package/src/components/waka-versus-card/index.tsx +1026 -0
  285. package/src/components/waka-video/index.tsx +557 -0
  286. package/src/components/waka-video-call/index.tsx +1087 -0
  287. package/src/components/waka-virtual-list/index.tsx +327 -0
  288. package/src/components/waka-voice-message/index.tsx +1019 -0
  289. package/src/components/waka-welcome-modal/index.tsx +790 -0
  290. package/src/components/waka-xp-bar/index.tsx +799 -0
@@ -0,0 +1,281 @@
1
+ "use client"
2
+
3
+ /**
4
+ * DataTableSyncStatus - Indicateur de statut de synchronisation
5
+ */
6
+
7
+ import * as React from "react"
8
+ import {
9
+ Cloud,
10
+ CloudOff,
11
+ RefreshCw,
12
+ AlertCircle,
13
+ Check,
14
+ Upload,
15
+ } from "lucide-react"
16
+ import { cn } from "../../utils/cn"
17
+ import { Button } from "../button"
18
+ import {
19
+ Tooltip,
20
+ TooltipContent,
21
+ TooltipProvider,
22
+ TooltipTrigger,
23
+ } from "../tooltip"
24
+ import { Badge } from "../badge"
25
+ import type { OfflineState } from "./hooks/useDataTableOffline"
26
+
27
+ export interface DataTableSyncStatusProps<TData> {
28
+ /** État offline */
29
+ state: OfflineState<TData>
30
+ /** Callback de synchronisation */
31
+ onSync?: () => void
32
+ /** Afficher les détails */
33
+ showDetails?: boolean
34
+ /** Classes CSS additionnelles */
35
+ className?: string
36
+ /** Variante d'affichage */
37
+ variant?: "default" | "compact" | "badge"
38
+ /** Labels personnalisés */
39
+ labels?: {
40
+ online?: string
41
+ offline?: string
42
+ syncing?: string
43
+ pending?: string
44
+ conflicts?: string
45
+ sync?: string
46
+ lastSync?: string
47
+ }
48
+ }
49
+
50
+ const DEFAULT_LABELS = {
51
+ online: "En ligne",
52
+ offline: "Hors ligne",
53
+ syncing: "Synchronisation...",
54
+ pending: "en attente",
55
+ conflicts: "conflits",
56
+ sync: "Synchroniser",
57
+ lastSync: "Dernière sync",
58
+ }
59
+
60
+ /**
61
+ * Indicateur de statut de synchronisation pour le mode offline
62
+ */
63
+ export function DataTableSyncStatus<TData>({
64
+ state,
65
+ onSync,
66
+ showDetails = true,
67
+ className,
68
+ variant = "default",
69
+ labels: customLabels,
70
+ }: DataTableSyncStatusProps<TData>) {
71
+ const labels = { ...DEFAULT_LABELS, ...customLabels }
72
+
73
+ const {
74
+ isOnline,
75
+ isSyncing,
76
+ pendingChangesCount,
77
+ conflicts,
78
+ syncError,
79
+ lastSyncTime,
80
+ isAvailable,
81
+ } = state
82
+
83
+ // Si IndexedDB n'est pas disponible, ne rien afficher
84
+ if (!isAvailable) {
85
+ return null
86
+ }
87
+
88
+ // Variante badge (très compact)
89
+ if (variant === "badge") {
90
+ return (
91
+ <TooltipProvider>
92
+ <Tooltip>
93
+ <TooltipTrigger asChild>
94
+ <Badge
95
+ variant={isOnline ? "outline" : "secondary"}
96
+ className={cn(
97
+ "gap-1",
98
+ !isOnline && "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
99
+ className
100
+ )}
101
+ >
102
+ {isOnline ? (
103
+ <Cloud className="h-3 w-3" />
104
+ ) : (
105
+ <CloudOff className="h-3 w-3" />
106
+ )}
107
+ {pendingChangesCount > 0 && (
108
+ <span className="ml-1">{pendingChangesCount}</span>
109
+ )}
110
+ </Badge>
111
+ </TooltipTrigger>
112
+ <TooltipContent>
113
+ <p>{isOnline ? labels.online : labels.offline}</p>
114
+ {pendingChangesCount > 0 && (
115
+ <p className="text-xs text-muted-foreground">
116
+ {pendingChangesCount} {labels.pending}
117
+ </p>
118
+ )}
119
+ </TooltipContent>
120
+ </Tooltip>
121
+ </TooltipProvider>
122
+ )
123
+ }
124
+
125
+ // Variante compact
126
+ if (variant === "compact") {
127
+ return (
128
+ <div className={cn("flex items-center gap-2", className)}>
129
+ <div
130
+ className={cn(
131
+ "flex items-center gap-1 text-sm",
132
+ isOnline ? "text-green-600 dark:text-green-400" : "text-yellow-600 dark:text-yellow-400"
133
+ )}
134
+ >
135
+ {isOnline ? (
136
+ <Cloud className="h-4 w-4" />
137
+ ) : (
138
+ <CloudOff className="h-4 w-4" />
139
+ )}
140
+ </div>
141
+
142
+ {pendingChangesCount > 0 && (
143
+ <Badge variant="secondary" className="h-5 px-1.5 text-xs">
144
+ {pendingChangesCount}
145
+ </Badge>
146
+ )}
147
+
148
+ {conflicts.length > 0 && (
149
+ <Badge variant="destructive" className="h-5 px-1.5 text-xs">
150
+ {conflicts.length}!
151
+ </Badge>
152
+ )}
153
+
154
+ {onSync && pendingChangesCount > 0 && isOnline && (
155
+ <Button
156
+ variant="ghost"
157
+ size="icon"
158
+ className="h-6 w-6"
159
+ onClick={onSync}
160
+ disabled={isSyncing}
161
+ >
162
+ <RefreshCw className={cn("h-3 w-3", isSyncing && "animate-spin")} />
163
+ </Button>
164
+ )}
165
+ </div>
166
+ )
167
+ }
168
+
169
+ // Variante default (complète)
170
+ return (
171
+ <div
172
+ className={cn(
173
+ "flex items-center gap-3 rounded-lg border p-2 text-sm",
174
+ isOnline ? "bg-green-50 dark:bg-green-950/20" : "bg-yellow-50 dark:bg-yellow-950/20",
175
+ className
176
+ )}
177
+ >
178
+ {/* Status icon */}
179
+ <div
180
+ className={cn(
181
+ "flex items-center gap-1.5",
182
+ isOnline ? "text-green-600 dark:text-green-400" : "text-yellow-600 dark:text-yellow-400"
183
+ )}
184
+ >
185
+ {isSyncing ? (
186
+ <RefreshCw className="h-4 w-4 animate-spin" />
187
+ ) : isOnline ? (
188
+ <Cloud className="h-4 w-4" />
189
+ ) : (
190
+ <CloudOff className="h-4 w-4" />
191
+ )}
192
+ <span className="font-medium">
193
+ {isSyncing ? labels.syncing : isOnline ? labels.online : labels.offline}
194
+ </span>
195
+ </div>
196
+
197
+ {/* Details */}
198
+ {showDetails && (
199
+ <>
200
+ {/* Pending changes */}
201
+ {pendingChangesCount > 0 && (
202
+ <div className="flex items-center gap-1 text-muted-foreground">
203
+ <Upload className="h-3.5 w-3.5" />
204
+ <span>
205
+ {pendingChangesCount} {labels.pending}
206
+ </span>
207
+ </div>
208
+ )}
209
+
210
+ {/* Conflicts */}
211
+ {conflicts.length > 0 && (
212
+ <div className="flex items-center gap-1 text-destructive">
213
+ <AlertCircle className="h-3.5 w-3.5" />
214
+ <span>
215
+ {conflicts.length} {labels.conflicts}
216
+ </span>
217
+ </div>
218
+ )}
219
+
220
+ {/* Last sync */}
221
+ {lastSyncTime && pendingChangesCount === 0 && (
222
+ <div className="flex items-center gap-1 text-muted-foreground">
223
+ <Check className="h-3.5 w-3.5 text-green-600" />
224
+ <span className="text-xs">
225
+ {labels.lastSync}: {formatRelativeTime(lastSyncTime)}
226
+ </span>
227
+ </div>
228
+ )}
229
+
230
+ {/* Error */}
231
+ {syncError && (
232
+ <div className="flex items-center gap-1 text-destructive text-xs">
233
+ <AlertCircle className="h-3.5 w-3.5" />
234
+ <span>{syncError}</span>
235
+ </div>
236
+ )}
237
+ </>
238
+ )}
239
+
240
+ {/* Sync button */}
241
+ {onSync && pendingChangesCount > 0 && isOnline && (
242
+ <Button
243
+ variant="outline"
244
+ size="sm"
245
+ className="ml-auto h-7"
246
+ onClick={onSync}
247
+ disabled={isSyncing}
248
+ >
249
+ <RefreshCw className={cn("mr-1.5 h-3.5 w-3.5", isSyncing && "animate-spin")} />
250
+ {labels.sync}
251
+ </Button>
252
+ )}
253
+ </div>
254
+ )
255
+ }
256
+
257
+ /**
258
+ * Formate un temps relatif (ex: "il y a 5 min")
259
+ */
260
+ function formatRelativeTime(date: Date): string {
261
+ const now = new Date()
262
+ const diffMs = now.getTime() - date.getTime()
263
+ const diffSeconds = Math.floor(diffMs / 1000)
264
+ const diffMinutes = Math.floor(diffSeconds / 60)
265
+ const diffHours = Math.floor(diffMinutes / 60)
266
+ const diffDays = Math.floor(diffHours / 24)
267
+
268
+ if (diffSeconds < 60) {
269
+ return "à l'instant"
270
+ } else if (diffMinutes < 60) {
271
+ return `il y a ${diffMinutes} min`
272
+ } else if (diffHours < 24) {
273
+ return `il y a ${diffHours}h`
274
+ } else {
275
+ return `il y a ${diffDays}j`
276
+ }
277
+ }
278
+
279
+ DataTableSyncStatus.displayName = "DataTableSyncStatus"
280
+
281
+ export default DataTableSyncStatus
@@ -0,0 +1,262 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Table } from "@tanstack/react-table"
5
+ import { X, Search, RefreshCw, Download, Upload, Columns, SlidersHorizontal } from "lucide-react"
6
+ import { Button } from "../button"
7
+ import { Input } from "../input"
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuCheckboxItem,
11
+ DropdownMenuContent,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuTrigger,
15
+ } from "../dropdown-menu"
16
+ import { useTranslation } from "react-i18next"
17
+ import { ToolbarConfig, TableAction, DataTableDensity } from "./types"
18
+ import { exportToCSV, exportToJSON } from "./utils"
19
+
20
+ interface DataTableToolbarProps<TData> {
21
+ table: Table<TData>
22
+ config?: ToolbarConfig
23
+ globalFilter: string
24
+ onGlobalFilterChange: (value: string) => void
25
+ density?: DataTableDensity
26
+ onDensityChange?: (density: DataTableDensity) => void
27
+ onExport?: (format: string) => void
28
+ onImport?: (file: File) => void
29
+ }
30
+
31
+ export function DataTableToolbar<TData>({
32
+ table,
33
+ config,
34
+ globalFilter,
35
+ onGlobalFilterChange,
36
+ density,
37
+ onDensityChange,
38
+ onExport,
39
+ onImport,
40
+ }: DataTableToolbarProps<TData>) {
41
+ const { t } = useTranslation()
42
+ const fileInputRef = React.useRef<HTMLInputElement>(null)
43
+
44
+ const selectedRows = table.getFilteredSelectedRowModel().rows
45
+ const hasSelection = selectedRows.length > 0
46
+
47
+ // Actions filtrées selon sélection
48
+ const visibleActions = React.useMemo(() => {
49
+ if (!config?.actions) return []
50
+ return config.actions.filter((action) => {
51
+ if (action.requiresSelection && !hasSelection) return false
52
+ return true
53
+ })
54
+ }, [config?.actions, hasSelection])
55
+
56
+ // Handler d'export
57
+ const handleExport = (format: "csv" | "json") => {
58
+ const data = hasSelection
59
+ ? selectedRows.map((row) => row.original)
60
+ : table.getFilteredRowModel().rows.map((row) => row.original)
61
+
62
+ if (onExport) {
63
+ onExport(format)
64
+ } else {
65
+ if (format === "csv") {
66
+ exportToCSV(data as any, "export.csv")
67
+ } else {
68
+ exportToJSON(data, "export.json")
69
+ }
70
+ }
71
+ }
72
+
73
+ // Handler d'import
74
+ const handleImport = (e: React.ChangeEvent<HTMLInputElement>) => {
75
+ const file = e.target.files?.[0]
76
+ if (file && onImport) {
77
+ onImport(file)
78
+ }
79
+ // Reset input
80
+ if (fileInputRef.current) {
81
+ fileInputRef.current.value = ""
82
+ }
83
+ }
84
+
85
+ return (
86
+ <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 p-4 border-b">
87
+ <div className="flex flex-1 flex-col sm:flex-row items-start sm:items-center gap-2 w-full sm:w-auto">
88
+ {/* Recherche globale */}
89
+ {config?.showSearch !== false && (
90
+ <div className="relative w-full sm:w-64">
91
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
92
+ <Input
93
+ placeholder={t("datatable.toolbar.search", { defaultValue: "Search..." })}
94
+ value={globalFilter ?? ""}
95
+ onChange={(e) => onGlobalFilterChange(e.target.value)}
96
+ className="pl-8 h-9"
97
+ />
98
+ {globalFilter && (
99
+ <button
100
+ onClick={() => onGlobalFilterChange("")}
101
+ className="absolute right-2 top-2.5 text-muted-foreground hover:text-foreground"
102
+ >
103
+ <X className="h-4 w-4" />
104
+ </button>
105
+ )}
106
+ </div>
107
+ )}
108
+
109
+ {/* Info de sélection */}
110
+ {hasSelection && (
111
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
112
+ <span>
113
+ {selectedRows.length} {t("datatable.toolbar.selected", { defaultValue: "selected" })}
114
+ </span>
115
+ <Button
116
+ variant="ghost"
117
+ size="sm"
118
+ onClick={() => table.resetRowSelection()}
119
+ className="h-7 px-2"
120
+ >
121
+ {t("datatable.toolbar.clear", { defaultValue: "Clear" })}
122
+ </Button>
123
+ </div>
124
+ )}
125
+ </div>
126
+
127
+ <div className="flex items-center gap-2">
128
+ {/* Actions personnalisées */}
129
+ {visibleActions.map((action) => (
130
+ <Button
131
+ key={action.id}
132
+ variant={action.variant || "outline"}
133
+ size="sm"
134
+ onClick={() => action.onClick(selectedRows.map((r) => r.original))}
135
+ disabled={action.disabled}
136
+ className="h-9"
137
+ >
138
+ {action.icon}
139
+ <span className="ml-2">{action.label}</span>
140
+ </Button>
141
+ ))}
142
+
143
+ {/* Bouton refresh */}
144
+ {config?.showRefresh && (
145
+ <Button
146
+ variant="outline"
147
+ size="icon"
148
+ onClick={config.onRefresh}
149
+ className="h-9 w-9"
150
+ >
151
+ <RefreshCw className="h-4 w-4" />
152
+ <span className="sr-only">{t("datatable.toolbar.refresh", { defaultValue: "Refresh" })}</span>
153
+ </Button>
154
+ )}
155
+
156
+ {/* Export */}
157
+ {onExport && (
158
+ <DropdownMenu>
159
+ <DropdownMenuTrigger asChild>
160
+ <Button variant="outline" size="icon" className="h-9 w-9">
161
+ <Download className="h-4 w-4" />
162
+ <span className="sr-only">{t("datatable.toolbar.export", { defaultValue: "Export" })}</span>
163
+ </Button>
164
+ </DropdownMenuTrigger>
165
+ <DropdownMenuContent align="end">
166
+ <DropdownMenuLabel>{t("datatable.toolbar.exportAs", { defaultValue: "Export as" })}</DropdownMenuLabel>
167
+ <DropdownMenuSeparator />
168
+ <DropdownMenuCheckboxItem onClick={() => handleExport("csv")}>
169
+ CSV
170
+ </DropdownMenuCheckboxItem>
171
+ <DropdownMenuCheckboxItem onClick={() => handleExport("json")}>
172
+ JSON
173
+ </DropdownMenuCheckboxItem>
174
+ </DropdownMenuContent>
175
+ </DropdownMenu>
176
+ )}
177
+
178
+ {/* Import */}
179
+ {onImport && (
180
+ <>
181
+ <input
182
+ ref={fileInputRef}
183
+ type="file"
184
+ accept=".csv,.json"
185
+ onChange={handleImport}
186
+ className="hidden"
187
+ />
188
+ <Button
189
+ variant="outline"
190
+ size="icon"
191
+ onClick={() => fileInputRef.current?.click()}
192
+ className="h-9 w-9"
193
+ >
194
+ <Upload className="h-4 w-4" />
195
+ <span className="sr-only">{t("datatable.toolbar.import", { defaultValue: "Import" })}</span>
196
+ </Button>
197
+ </>
198
+ )}
199
+
200
+ {/* Sélecteur de colonnes */}
201
+ {config?.showColumnToggle !== false && (
202
+ <DropdownMenu>
203
+ <DropdownMenuTrigger asChild>
204
+ <Button variant="outline" size="icon" className="h-9 w-9">
205
+ <Columns className="h-4 w-4" />
206
+ <span className="sr-only">{t("datatable.toolbar.columns", { defaultValue: "Columns" })}</span>
207
+ </Button>
208
+ </DropdownMenuTrigger>
209
+ <DropdownMenuContent align="end" className="w-48">
210
+ <DropdownMenuLabel>{t("datatable.toolbar.toggleColumns", { defaultValue: "Toggle columns" })}</DropdownMenuLabel>
211
+ <DropdownMenuSeparator />
212
+ {table
213
+ .getAllColumns()
214
+ .filter((column) => column.getCanHide())
215
+ .map((column) => {
216
+ return (
217
+ <DropdownMenuCheckboxItem
218
+ key={column.id}
219
+ className="capitalize"
220
+ checked={column.getIsVisible()}
221
+ onCheckedChange={(value) => column.toggleVisibility(!!value)}
222
+ >
223
+ {column.id}
224
+ </DropdownMenuCheckboxItem>
225
+ )
226
+ })}
227
+ </DropdownMenuContent>
228
+ </DropdownMenu>
229
+ )}
230
+
231
+ {/* Densité */}
232
+ {config?.showDensity && onDensityChange && (
233
+ <DropdownMenu>
234
+ <DropdownMenuTrigger asChild>
235
+ <Button variant="outline" size="icon" className="h-9 w-9">
236
+ <SlidersHorizontal className="h-4 w-4" />
237
+ <span className="sr-only">{t("datatable.toolbar.density", { defaultValue: "Density" })}</span>
238
+ </Button>
239
+ </DropdownMenuTrigger>
240
+ <DropdownMenuContent align="end">
241
+ <DropdownMenuLabel>{t("datatable.toolbar.tableDensity", { defaultValue: "Table density" })}</DropdownMenuLabel>
242
+ <DropdownMenuSeparator />
243
+ <DropdownMenuCheckboxItem
244
+ checked={density === "comfortable"}
245
+ onCheckedChange={() => onDensityChange("comfortable")}
246
+ >
247
+ {t("datatable.toolbar.comfortable", { defaultValue: "Comfortable" })}
248
+ </DropdownMenuCheckboxItem>
249
+ <DropdownMenuCheckboxItem
250
+ checked={density === "compact"}
251
+ onCheckedChange={() => onDensityChange("compact")}
252
+ >
253
+ {t("datatable.toolbar.compact", { defaultValue: "Compact" })}
254
+ </DropdownMenuCheckboxItem>
255
+ </DropdownMenuContent>
256
+ </DropdownMenu>
257
+ )}
258
+ </div>
259
+ </div>
260
+ )
261
+ }
262
+