@wakastellar/ui 2.0.0 → 2.1.1

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 (291) hide show
  1. package/README.md +71 -8
  2. package/dist/cli/commands/add.d.ts +7 -0
  3. package/dist/cli/commands/init.d.ts +6 -0
  4. package/dist/cli/commands/list.d.ts +5 -0
  5. package/dist/cli/commands/search.d.ts +1 -0
  6. package/dist/cli/index.cjs +6014 -0
  7. package/dist/cli/index.d.ts +1 -0
  8. package/dist/cli/utils/config.d.ts +29 -0
  9. package/dist/cli/utils/logger.d.ts +20 -0
  10. package/dist/cli/utils/registry.d.ts +23 -0
  11. package/package.json +14 -3
  12. package/src/blocks/activity-timeline/index.tsx +586 -0
  13. package/src/blocks/calendar-view/index.tsx +756 -0
  14. package/src/blocks/chat/index.tsx +1018 -0
  15. package/src/blocks/chat/widget.tsx +504 -0
  16. package/src/blocks/dashboard/index.tsx +522 -0
  17. package/src/blocks/empty-states/index.tsx +452 -0
  18. package/src/blocks/error-pages/index.tsx +426 -0
  19. package/src/blocks/faq/index.tsx +479 -0
  20. package/src/blocks/file-manager/index.tsx +890 -0
  21. package/src/blocks/footer/index.tsx +133 -0
  22. package/src/blocks/header/index.tsx +357 -0
  23. package/src/blocks/headtab/index.tsx +139 -0
  24. package/src/blocks/i18n-editor/index.tsx +1016 -0
  25. package/src/blocks/index.ts +80 -0
  26. package/src/blocks/kanban-board/index.tsx +779 -0
  27. package/src/blocks/landing/index.tsx +677 -0
  28. package/src/blocks/language-selector/index.tsx +88 -0
  29. package/src/blocks/layout/index.tsx +159 -0
  30. package/src/blocks/login/index.tsx +339 -0
  31. package/src/blocks/login/types.ts +131 -0
  32. package/src/blocks/pricing/index.tsx +564 -0
  33. package/src/blocks/profile/index.tsx +746 -0
  34. package/src/blocks/settings/index.tsx +558 -0
  35. package/src/blocks/sidebar/index.tsx +713 -0
  36. package/src/blocks/theme-creator-block/index.tsx +835 -0
  37. package/src/blocks/user-management/index.tsx +1037 -0
  38. package/src/blocks/wizard/index.tsx +719 -0
  39. package/src/components/DataTable/DataTable.tsx +406 -0
  40. package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
  41. package/src/components/DataTable/DataTableBody.tsx +216 -0
  42. package/src/components/DataTable/DataTableCell.tsx +172 -0
  43. package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
  44. package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
  45. package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
  46. package/src/components/DataTable/DataTableEditCell.tsx +279 -0
  47. package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
  48. package/src/components/DataTable/DataTableFilters.tsx +535 -0
  49. package/src/components/DataTable/DataTableGrouping.tsx +147 -0
  50. package/src/components/DataTable/DataTableHeader.tsx +172 -0
  51. package/src/components/DataTable/DataTablePagination.tsx +125 -0
  52. package/src/components/DataTable/DataTableSelection.tsx +269 -0
  53. package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
  54. package/src/components/DataTable/DataTableToolbar.tsx +262 -0
  55. package/src/components/DataTable/README.md +446 -0
  56. package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
  57. package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
  58. package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
  59. package/src/components/DataTable/examples/EditExample.tsx +166 -0
  60. package/src/components/DataTable/formatters/index.ts +335 -0
  61. package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
  62. package/src/components/DataTable/hooks/useDataTable.ts +145 -0
  63. package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
  64. package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
  65. package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
  66. package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
  67. package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
  68. package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
  69. package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
  70. package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
  71. package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
  72. package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
  73. package/src/components/DataTable/index.ts +81 -0
  74. package/src/components/DataTable/services/IndexedDBService.ts +504 -0
  75. package/src/components/DataTable/templates/index.tsx +803 -0
  76. package/src/components/DataTable/types.ts +504 -0
  77. package/src/components/DataTable/utils.ts +164 -0
  78. package/src/components/DataTable/workers/exportWorker.ts +213 -0
  79. package/src/components/accordion/index.tsx +61 -0
  80. package/src/components/alert/index.tsx +61 -0
  81. package/src/components/alert-dialog/index.tsx +146 -0
  82. package/src/components/aspect-ratio/index.tsx +12 -0
  83. package/src/components/avatar/index.tsx +54 -0
  84. package/src/components/badge/Badge.stories.tsx +64 -0
  85. package/src/components/badge/index.tsx +38 -0
  86. package/src/components/button/Button.stories.tsx +173 -0
  87. package/src/components/button/index.tsx +56 -0
  88. package/src/components/calendar/index.tsx +73 -0
  89. package/src/components/card/index.tsx +78 -0
  90. package/src/components/checkbox/index.tsx +34 -0
  91. package/src/components/code/index.tsx +229 -0
  92. package/src/components/collapsible/index.tsx +16 -0
  93. package/src/components/command/index.tsx +162 -0
  94. package/src/components/context-menu/index.tsx +204 -0
  95. package/src/components/dialog/index.tsx +126 -0
  96. package/src/components/dropdown-menu/index.tsx +204 -0
  97. package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
  98. package/src/components/error-boundary/index.ts +7 -0
  99. package/src/components/form/index.tsx +183 -0
  100. package/src/components/hover-card/index.tsx +33 -0
  101. package/src/components/index.ts +368 -0
  102. package/src/components/input/Input.stories.tsx +100 -0
  103. package/src/components/input/index.tsx +27 -0
  104. package/src/components/input-otp/index.tsx +277 -0
  105. package/src/components/label/index.tsx +30 -0
  106. package/src/components/language-selector/index.tsx +341 -0
  107. package/src/components/menubar/index.tsx +240 -0
  108. package/src/components/navigation-menu/index.tsx +134 -0
  109. package/src/components/popover/index.tsx +35 -0
  110. package/src/components/progress/index.tsx +32 -0
  111. package/src/components/radio-group/index.tsx +48 -0
  112. package/src/components/scroll-area/index.tsx +52 -0
  113. package/src/components/select/index.tsx +164 -0
  114. package/src/components/separator/index.tsx +35 -0
  115. package/src/components/sheet/index.tsx +147 -0
  116. package/src/components/skeleton/index.tsx +22 -0
  117. package/src/components/slider/index.tsx +32 -0
  118. package/src/components/switch/index.tsx +33 -0
  119. package/src/components/table/index.tsx +117 -0
  120. package/src/components/tabs/index.tsx +59 -0
  121. package/src/components/textarea/index.tsx +30 -0
  122. package/src/components/theme-selector/index.tsx +327 -0
  123. package/src/components/toast/index.tsx +133 -0
  124. package/src/components/toaster/index.tsx +34 -0
  125. package/src/components/toggle/index.tsx +49 -0
  126. package/src/components/tooltip/index.tsx +34 -0
  127. package/src/components/typography/index.tsx +276 -0
  128. package/src/components/waka-3d-pie-chart/index.tsx +486 -0
  129. package/src/components/waka-achievement-unlock/index.tsx +716 -0
  130. package/src/components/waka-activity-feed/index.tsx +686 -0
  131. package/src/components/waka-address-autocomplete/index.tsx +1202 -0
  132. package/src/components/waka-admincrumb/index.tsx +349 -0
  133. package/src/components/waka-alert-stack/index.tsx +827 -0
  134. package/src/components/waka-allocation-matrix/index.tsx +1278 -0
  135. package/src/components/waka-approval-chain/index.tsx +766 -0
  136. package/src/components/waka-audit-log/index.tsx +1475 -0
  137. package/src/components/waka-autocomplete/index.tsx +358 -0
  138. package/src/components/waka-badge-showcase/index.tsx +704 -0
  139. package/src/components/waka-barcode/index.tsx +260 -0
  140. package/src/components/waka-biometric-prompt/index.tsx +765 -0
  141. package/src/components/waka-bottom-sheet/index.tsx +495 -0
  142. package/src/components/waka-breadcrumb/index.tsx +376 -0
  143. package/src/components/waka-breadcrumb-path/index.tsx +513 -0
  144. package/src/components/waka-budget-burn/index.tsx +1234 -0
  145. package/src/components/waka-capacity-planner/index.tsx +1107 -0
  146. package/src/components/waka-carousel/index.tsx +893 -0
  147. package/src/components/waka-cart-summary/index.tsx +1055 -0
  148. package/src/components/waka-challenge-timer/index.tsx +1044 -0
  149. package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
  150. package/src/components/waka-charts/WakaBarChart.tsx +222 -0
  151. package/src/components/waka-charts/WakaChart.tsx +124 -0
  152. package/src/components/waka-charts/WakaLineChart.tsx +219 -0
  153. package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
  154. package/src/components/waka-charts/WakaPieChart.tsx +214 -0
  155. package/src/components/waka-charts/WakaSparkline.tsx +229 -0
  156. package/src/components/waka-charts/dataTableHelpers.ts +109 -0
  157. package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
  158. package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
  159. package/src/components/waka-charts/index.ts +90 -0
  160. package/src/components/waka-charts/types.ts +330 -0
  161. package/src/components/waka-chat-bubble/index.tsx +1060 -0
  162. package/src/components/waka-checklist/index.tsx +1067 -0
  163. package/src/components/waka-checkout-stepper/index.tsx +976 -0
  164. package/src/components/waka-cohort-table/index.tsx +1011 -0
  165. package/src/components/waka-color-picker/index.tsx +447 -0
  166. package/src/components/waka-combo-counter/index.tsx +864 -0
  167. package/src/components/waka-combobox/index.tsx +497 -0
  168. package/src/components/waka-command-bar/index.tsx +403 -0
  169. package/src/components/waka-compare-period/index.tsx +1230 -0
  170. package/src/components/waka-connection-matrix/index.tsx +1053 -0
  171. package/src/components/waka-contribution-graph/index.tsx +552 -0
  172. package/src/components/waka-cost-breakdown/index.tsx +1065 -0
  173. package/src/components/waka-coupon-input/index.tsx +592 -0
  174. package/src/components/waka-credit-card-input/index.tsx +982 -0
  175. package/src/components/waka-daily-reward/index.tsx +762 -0
  176. package/src/components/waka-date-range-picker/index.tsx +378 -0
  177. package/src/components/waka-datetime-picker/index.tsx +793 -0
  178. package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
  179. package/src/components/waka-deployment-lane/index.tsx +673 -0
  180. package/src/components/waka-device-trust/index.tsx +1259 -0
  181. package/src/components/waka-dock/index.tsx +285 -0
  182. package/src/components/waka-drawer/index.tsx +319 -0
  183. package/src/components/waka-empty-state/index.tsx +545 -0
  184. package/src/components/waka-error-shake/index.tsx +398 -0
  185. package/src/components/waka-feature-announcement/index.tsx +991 -0
  186. package/src/components/waka-file-upload/index.tsx +437 -0
  187. package/src/components/waka-floating-nav/index.tsx +413 -0
  188. package/src/components/waka-flow-diagram/index.tsx +508 -0
  189. package/src/components/waka-funnel-chart/index.tsx +823 -0
  190. package/src/components/waka-glow-card/index.tsx +246 -0
  191. package/src/components/waka-goal-progress/index.tsx +1025 -0
  192. package/src/components/waka-haptic-button/index.tsx +388 -0
  193. package/src/components/waka-health-pulse/index.tsx +451 -0
  194. package/src/components/waka-heatmap/index.tsx +1026 -0
  195. package/src/components/waka-hotspot/index.tsx +682 -0
  196. package/src/components/waka-image/index.tsx +373 -0
  197. package/src/components/waka-incident-timeline/index.tsx +686 -0
  198. package/src/components/waka-invoice-preview/index.tsx +829 -0
  199. package/src/components/waka-kanban/index.tsx +646 -0
  200. package/src/components/waka-kpi-dashboard/index.tsx +755 -0
  201. package/src/components/waka-leaderboard/index.tsx +746 -0
  202. package/src/components/waka-level-progress/index.tsx +665 -0
  203. package/src/components/waka-liquid-button/index.tsx +520 -0
  204. package/src/components/waka-loading-orbit/index.tsx +478 -0
  205. package/src/components/waka-loot-box/index.tsx +1091 -0
  206. package/src/components/waka-magic-link/index.tsx +321 -0
  207. package/src/components/waka-magnetic-button/index.tsx +567 -0
  208. package/src/components/waka-mention-input/index.tsx +953 -0
  209. package/src/components/waka-metric-sparkline/index.tsx +627 -0
  210. package/src/components/waka-milestone-road/index.tsx +1064 -0
  211. package/src/components/waka-modal/index.tsx +374 -0
  212. package/src/components/waka-morph-button/index.tsx +495 -0
  213. package/src/components/waka-network-topology/index.tsx +801 -0
  214. package/src/components/waka-notifications/index.tsx +414 -0
  215. package/src/components/waka-number-input/index.tsx +373 -0
  216. package/src/components/waka-orbital-menu/index.tsx +445 -0
  217. package/src/components/waka-order-tracker/index.tsx +1041 -0
  218. package/src/components/waka-pagination/index.tsx +393 -0
  219. package/src/components/waka-password-strength/index.tsx +824 -0
  220. package/src/components/waka-payment-method-picker/index.tsx +715 -0
  221. package/src/components/waka-permission-matrix/index.tsx +1302 -0
  222. package/src/components/waka-phone-input/index.tsx +801 -0
  223. package/src/components/waka-pipeline-view/index.tsx +604 -0
  224. package/src/components/waka-player-card/index.tsx +691 -0
  225. package/src/components/waka-points-popup/index.tsx +366 -0
  226. package/src/components/waka-power-up/index.tsx +1155 -0
  227. package/src/components/waka-presence-indicator/index.tsx +1181 -0
  228. package/src/components/waka-pricing-table/index.tsx +755 -0
  229. package/src/components/waka-product-card/index.tsx +786 -0
  230. package/src/components/waka-progress-onboarding/index.tsx +878 -0
  231. package/src/components/waka-pull-to-refresh/index.tsx +451 -0
  232. package/src/components/waka-qrcode/index.tsx +232 -0
  233. package/src/components/waka-quest-card/index.tsx +1275 -0
  234. package/src/components/waka-quota-bar/index.tsx +693 -0
  235. package/src/components/waka-radar-score/index.tsx +512 -0
  236. package/src/components/waka-rank-badge/index.tsx +813 -0
  237. package/src/components/waka-rating-input/index.tsx +560 -0
  238. package/src/components/waka-reaction-picker/index.tsx +1062 -0
  239. package/src/components/waka-region-map/index.tsx +730 -0
  240. package/src/components/waka-resource-gauge/index.tsx +654 -0
  241. package/src/components/waka-resource-pool/index.tsx +1035 -0
  242. package/src/components/waka-rich-text-editor/index.tsx +594 -0
  243. package/src/components/waka-rollback-slider/index.tsx +891 -0
  244. package/src/components/waka-sankey-diagram/index.tsx +1032 -0
  245. package/src/components/waka-schedule-picker/index.tsx +1060 -0
  246. package/src/components/waka-scratch-card/index.tsx +914 -0
  247. package/src/components/waka-season-pass/index.tsx +886 -0
  248. package/src/components/waka-security-score/index.tsx +1126 -0
  249. package/src/components/waka-segmented-control/index.tsx +238 -0
  250. package/src/components/waka-server-rack/index.tsx +764 -0
  251. package/src/components/waka-session-manager/index.tsx +815 -0
  252. package/src/components/waka-signature-pad/index.tsx +744 -0
  253. package/src/components/waka-skeleton-wave/index.tsx +454 -0
  254. package/src/components/waka-skill-tree/index.tsx +1031 -0
  255. package/src/components/waka-sla-tracker/index.tsx +798 -0
  256. package/src/components/waka-slider-range/index.tsx +765 -0
  257. package/src/components/waka-spin-wheel/index.tsx +671 -0
  258. package/src/components/waka-spinner/index.tsx +284 -0
  259. package/src/components/waka-spotlight/index.tsx +410 -0
  260. package/src/components/waka-stat/index.tsx +428 -0
  261. package/src/components/waka-stats-hexagon/index.tsx +824 -0
  262. package/src/components/waka-status-matrix/index.tsx +565 -0
  263. package/src/components/waka-stepper/index.tsx +489 -0
  264. package/src/components/waka-streak-counter/index.tsx +334 -0
  265. package/src/components/waka-success-explosion/index.tsx +453 -0
  266. package/src/components/waka-swipe-card/index.tsx +574 -0
  267. package/src/components/waka-tabs-morph/index.tsx +509 -0
  268. package/src/components/waka-tag-input/index.tsx +877 -0
  269. package/src/components/waka-team-banner/index.tsx +1183 -0
  270. package/src/components/waka-terminal-output/index.tsx +836 -0
  271. package/src/components/waka-theme-creator/index.tsx +762 -0
  272. package/src/components/waka-theme-manager/index.tsx +654 -0
  273. package/src/components/waka-thread-view/index.tsx +874 -0
  274. package/src/components/waka-tilt-card/index.tsx +250 -0
  275. package/src/components/waka-time-picker/index.tsx +479 -0
  276. package/src/components/waka-timeline/index.tsx +385 -0
  277. package/src/components/waka-tooltip-tour/index.tsx +855 -0
  278. package/src/components/waka-tour-guide/index.tsx +920 -0
  279. package/src/components/waka-tournament-bracket/index.tsx +1276 -0
  280. package/src/components/waka-tree/index.tsx +557 -0
  281. package/src/components/waka-treemap-chart/index.tsx +1031 -0
  282. package/src/components/waka-two-factor-setup/index.tsx +995 -0
  283. package/src/components/waka-typewriter/index.tsx +566 -0
  284. package/src/components/waka-typing-indicator/index.tsx +649 -0
  285. package/src/components/waka-versus-card/index.tsx +1026 -0
  286. package/src/components/waka-video/index.tsx +557 -0
  287. package/src/components/waka-video-call/index.tsx +1087 -0
  288. package/src/components/waka-virtual-list/index.tsx +327 -0
  289. package/src/components/waka-voice-message/index.tsx +1019 -0
  290. package/src/components/waka-welcome-modal/index.tsx +790 -0
  291. package/src/components/waka-xp-bar/index.tsx +799 -0
@@ -0,0 +1,478 @@
1
+ "use client"
2
+
3
+ /**
4
+ * DataTableConflictResolver - Interface de résolution des conflits
5
+ */
6
+
7
+ import * as React from "react"
8
+ import { AlertTriangle, Check, X, GitMerge, ArrowRight } from "lucide-react"
9
+ import { cn } from "../../utils/cn"
10
+ import { Button } from "../button"
11
+ import {
12
+ Dialog,
13
+ DialogContent,
14
+ DialogDescription,
15
+ DialogFooter,
16
+ DialogHeader,
17
+ DialogTitle,
18
+ } from "../dialog"
19
+ import {
20
+ Sheet,
21
+ SheetContent,
22
+ SheetDescription,
23
+ SheetHeader,
24
+ SheetTitle,
25
+ } from "../sheet"
26
+ import { Badge } from "../badge"
27
+ import { Separator } from "../separator"
28
+ import { ScrollArea } from "../scroll-area"
29
+ import type { ConflictEntry } from "./services/IndexedDBService"
30
+
31
+ export interface DataTableConflictResolverProps<TData> {
32
+ /** Liste des conflits */
33
+ conflicts: ConflictEntry<TData>[]
34
+ /** Callback de résolution */
35
+ onResolve: (conflictId: string, resolution: "local" | "server" | "merge", mergedData?: Partial<TData>) => Promise<void>
36
+ /** Ouvert */
37
+ open?: boolean
38
+ /** Callback de fermeture */
39
+ onOpenChange?: (open: boolean) => void
40
+ /** Afficher en mode sheet (sidebar) ou dialog */
41
+ mode?: "dialog" | "sheet"
42
+ /** Labels personnalisés */
43
+ labels?: {
44
+ title?: string
45
+ description?: string
46
+ localVersion?: string
47
+ serverVersion?: string
48
+ keepLocal?: string
49
+ keepServer?: string
50
+ merge?: string
51
+ resolve?: string
52
+ skip?: string
53
+ noConflicts?: string
54
+ conflictType?: Record<"create" | "update" | "delete", string>
55
+ }
56
+ /** Fonction de rendu personnalisé pour les données */
57
+ renderData?: (data: Partial<TData> | null, type: "local" | "server") => React.ReactNode
58
+ /** Clés à afficher dans la comparaison */
59
+ displayKeys?: (keyof TData)[]
60
+ /** Labels des clés */
61
+ keyLabels?: Record<string, string>
62
+ }
63
+
64
+ const DEFAULT_LABELS = {
65
+ title: "Résolution des conflits",
66
+ description: "Des modifications ont été effectuées à la fois localement et sur le serveur. Choisissez quelle version conserver.",
67
+ localVersion: "Version locale",
68
+ serverVersion: "Version serveur",
69
+ keepLocal: "Garder local",
70
+ keepServer: "Garder serveur",
71
+ merge: "Fusionner",
72
+ resolve: "Résoudre",
73
+ skip: "Ignorer",
74
+ noConflicts: "Aucun conflit à résoudre",
75
+ conflictType: {
76
+ create: "Création",
77
+ update: "Modification",
78
+ delete: "Suppression",
79
+ },
80
+ }
81
+
82
+ /**
83
+ * Interface de résolution des conflits de synchronisation
84
+ */
85
+ export function DataTableConflictResolver<TData extends Record<string, unknown>>({
86
+ conflicts,
87
+ onResolve,
88
+ open = false,
89
+ onOpenChange,
90
+ mode = "dialog",
91
+ labels: customLabels,
92
+ renderData,
93
+ displayKeys,
94
+ keyLabels,
95
+ }: DataTableConflictResolverProps<TData>) {
96
+ const labels = { ...DEFAULT_LABELS, ...customLabels }
97
+ const [currentIndex, setCurrentIndex] = React.useState(0)
98
+ const [isResolving, setIsResolving] = React.useState(false)
99
+ const [mergeMode, setMergeMode] = React.useState(false)
100
+ const [mergedData, setMergedData] = React.useState<Partial<TData>>({})
101
+
102
+ const currentConflict = conflicts[currentIndex]
103
+ const totalConflicts = conflicts.length
104
+
105
+ // Reset quand les conflits changent
106
+ React.useEffect(() => {
107
+ if (conflicts.length === 0) {
108
+ setCurrentIndex(0)
109
+ } else if (currentIndex >= conflicts.length) {
110
+ setCurrentIndex(Math.max(0, conflicts.length - 1))
111
+ }
112
+ }, [conflicts, currentIndex])
113
+
114
+ // Reset merge mode quand le conflit change
115
+ React.useEffect(() => {
116
+ setMergeMode(false)
117
+ if (currentConflict) {
118
+ setMergedData({
119
+ ...(currentConflict.serverData || {}),
120
+ ...(currentConflict.localData || {}),
121
+ } as Partial<TData>)
122
+ }
123
+ }, [currentConflict])
124
+
125
+ const handleResolve = async (resolution: "local" | "server" | "merge") => {
126
+ if (!currentConflict) return
127
+
128
+ setIsResolving(true)
129
+ try {
130
+ await onResolve(
131
+ currentConflict.id,
132
+ resolution,
133
+ resolution === "merge" ? mergedData : undefined
134
+ )
135
+
136
+ // Passer au conflit suivant
137
+ if (currentIndex < totalConflicts - 1) {
138
+ setCurrentIndex((prev) => prev + 1)
139
+ }
140
+ } finally {
141
+ setIsResolving(false)
142
+ }
143
+ }
144
+
145
+ const handleSkip = () => {
146
+ if (currentIndex < totalConflicts - 1) {
147
+ setCurrentIndex((prev) => prev + 1)
148
+ }
149
+ }
150
+
151
+ // Contenu commun
152
+ const content = (
153
+ <>
154
+ {totalConflicts === 0 ? (
155
+ <div className="flex flex-col items-center justify-center py-8 text-center text-muted-foreground">
156
+ <Check className="h-12 w-12 text-green-500 mb-4" />
157
+ <p>{labels.noConflicts}</p>
158
+ </div>
159
+ ) : currentConflict ? (
160
+ <div className="space-y-4">
161
+ {/* Header avec navigation */}
162
+ <div className="flex items-center justify-between">
163
+ <Badge variant="outline" className="gap-1">
164
+ <AlertTriangle className="h-3 w-3" />
165
+ {labels.conflictType?.[currentConflict.type] || currentConflict.type}
166
+ </Badge>
167
+ <span className="text-sm text-muted-foreground">
168
+ {currentIndex + 1} / {totalConflicts}
169
+ </span>
170
+ </div>
171
+
172
+ {/* Info sur la ligne */}
173
+ <div className="text-sm text-muted-foreground">
174
+ ID: <code className="bg-muted px-1 rounded">{currentConflict.rowId}</code>
175
+ </div>
176
+
177
+ {mergeMode ? (
178
+ /* Mode fusion */
179
+ <MergeEditor
180
+ localData={currentConflict.localData}
181
+ serverData={currentConflict.serverData}
182
+ mergedData={mergedData}
183
+ onChange={setMergedData}
184
+ displayKeys={displayKeys as string[]}
185
+ keyLabels={keyLabels}
186
+ />
187
+ ) : (
188
+ /* Comparaison côte à côte */
189
+ <div className="grid grid-cols-2 gap-4">
190
+ {/* Version locale */}
191
+ <DataVersion
192
+ title={labels.localVersion}
193
+ data={currentConflict.localData}
194
+ timestamp={currentConflict.localTimestamp}
195
+ variant="local"
196
+ renderData={renderData}
197
+ displayKeys={displayKeys as string[]}
198
+ keyLabels={keyLabels}
199
+ />
200
+
201
+ {/* Version serveur */}
202
+ <DataVersion
203
+ title={labels.serverVersion}
204
+ data={currentConflict.serverData}
205
+ timestamp={currentConflict.serverTimestamp}
206
+ variant="server"
207
+ renderData={renderData}
208
+ displayKeys={displayKeys as string[]}
209
+ keyLabels={keyLabels}
210
+ />
211
+ </div>
212
+ )}
213
+
214
+ <Separator />
215
+
216
+ {/* Actions */}
217
+ <div className="flex items-center justify-between gap-2">
218
+ <Button variant="ghost" size="sm" onClick={handleSkip}>
219
+ {labels.skip}
220
+ </Button>
221
+
222
+ <div className="flex items-center gap-2">
223
+ {mergeMode ? (
224
+ <>
225
+ <Button variant="outline" size="sm" onClick={() => setMergeMode(false)}>
226
+ <X className="mr-1.5 h-3.5 w-3.5" />
227
+ Annuler
228
+ </Button>
229
+ <Button
230
+ size="sm"
231
+ onClick={() => handleResolve("merge")}
232
+ disabled={isResolving}
233
+ >
234
+ <Check className="mr-1.5 h-3.5 w-3.5" />
235
+ Appliquer fusion
236
+ </Button>
237
+ </>
238
+ ) : (
239
+ <>
240
+ <Button
241
+ variant="outline"
242
+ size="sm"
243
+ onClick={() => handleResolve("local")}
244
+ disabled={isResolving}
245
+ >
246
+ {labels.keepLocal}
247
+ </Button>
248
+ <Button
249
+ variant="outline"
250
+ size="sm"
251
+ onClick={() => setMergeMode(true)}
252
+ >
253
+ <GitMerge className="mr-1.5 h-3.5 w-3.5" />
254
+ {labels.merge}
255
+ </Button>
256
+ <Button
257
+ size="sm"
258
+ onClick={() => handleResolve("server")}
259
+ disabled={isResolving}
260
+ >
261
+ {labels.keepServer}
262
+ </Button>
263
+ </>
264
+ )}
265
+ </div>
266
+ </div>
267
+ </div>
268
+ ) : null}
269
+ </>
270
+ )
271
+
272
+ // Rendu en mode sheet
273
+ if (mode === "sheet") {
274
+ return (
275
+ <Sheet open={open} onOpenChange={onOpenChange}>
276
+ <SheetContent className="sm:max-w-xl">
277
+ <SheetHeader>
278
+ <SheetTitle className="flex items-center gap-2">
279
+ <AlertTriangle className="h-5 w-5 text-yellow-500" />
280
+ {labels.title}
281
+ </SheetTitle>
282
+ <SheetDescription>{labels.description}</SheetDescription>
283
+ </SheetHeader>
284
+ <div className="mt-6">{content}</div>
285
+ </SheetContent>
286
+ </Sheet>
287
+ )
288
+ }
289
+
290
+ // Rendu en mode dialog
291
+ return (
292
+ <Dialog open={open} onOpenChange={onOpenChange}>
293
+ <DialogContent className="max-w-2xl">
294
+ <DialogHeader>
295
+ <DialogTitle className="flex items-center gap-2">
296
+ <AlertTriangle className="h-5 w-5 text-yellow-500" />
297
+ {labels.title}
298
+ </DialogTitle>
299
+ <DialogDescription>{labels.description}</DialogDescription>
300
+ </DialogHeader>
301
+ {content}
302
+ </DialogContent>
303
+ </Dialog>
304
+ )
305
+ }
306
+
307
+ /**
308
+ * Affichage d'une version des données
309
+ */
310
+ function DataVersion<TData>({
311
+ title,
312
+ data,
313
+ timestamp,
314
+ variant,
315
+ renderData,
316
+ displayKeys,
317
+ keyLabels,
318
+ }: {
319
+ title: string
320
+ data: Partial<TData> | null
321
+ timestamp: Date
322
+ variant: "local" | "server"
323
+ renderData?: (data: Partial<TData> | null, type: "local" | "server") => React.ReactNode
324
+ displayKeys?: string[]
325
+ keyLabels?: Record<string, string>
326
+ }) {
327
+ return (
328
+ <div
329
+ className={cn(
330
+ "rounded-lg border p-3",
331
+ variant === "local" ? "border-blue-200 bg-blue-50/50 dark:border-blue-800 dark:bg-blue-950/20" : "border-green-200 bg-green-50/50 dark:border-green-800 dark:bg-green-950/20"
332
+ )}
333
+ >
334
+ <div className="flex items-center justify-between mb-2">
335
+ <span className="font-medium text-sm">{title}</span>
336
+ <span className="text-xs text-muted-foreground">
337
+ {new Date(timestamp).toLocaleString()}
338
+ </span>
339
+ </div>
340
+
341
+ <ScrollArea className="h-32">
342
+ {renderData ? (
343
+ renderData(data, variant)
344
+ ) : data ? (
345
+ <div className="space-y-1 text-sm">
346
+ {(displayKeys || Object.keys(data)).map((key) => {
347
+ const value = (data as Record<string, unknown>)[key]
348
+ if (value === undefined) return null
349
+ return (
350
+ <div key={key} className="flex justify-between gap-2">
351
+ <span className="text-muted-foreground">
352
+ {keyLabels?.[key] || key}:
353
+ </span>
354
+ <span className="font-mono truncate max-w-[150px]">
355
+ {formatValue(value)}
356
+ </span>
357
+ </div>
358
+ )
359
+ })}
360
+ </div>
361
+ ) : (
362
+ <span className="text-muted-foreground italic">Aucune donnée</span>
363
+ )}
364
+ </ScrollArea>
365
+ </div>
366
+ )
367
+ }
368
+
369
+ /**
370
+ * Éditeur de fusion
371
+ */
372
+ function MergeEditor<TData>({
373
+ localData,
374
+ serverData,
375
+ mergedData,
376
+ onChange,
377
+ displayKeys,
378
+ keyLabels,
379
+ }: {
380
+ localData: Partial<TData> | null
381
+ serverData: Partial<TData> | null
382
+ mergedData: Partial<TData>
383
+ onChange: (data: Partial<TData>) => void
384
+ displayKeys?: string[]
385
+ keyLabels?: Record<string, string>
386
+ }) {
387
+ const allKeys = displayKeys || [
388
+ ...new Set([
389
+ ...Object.keys(localData || {}),
390
+ ...Object.keys(serverData || {}),
391
+ ]),
392
+ ]
393
+
394
+ const handleFieldChange = (key: string, source: "local" | "server") => {
395
+ const value = source === "local"
396
+ ? (localData as Record<string, unknown>)?.[key]
397
+ : (serverData as Record<string, unknown>)?.[key]
398
+
399
+ onChange({
400
+ ...mergedData,
401
+ [key]: value,
402
+ })
403
+ }
404
+
405
+ return (
406
+ <div className="rounded-lg border p-4 bg-muted/30">
407
+ <div className="text-sm font-medium mb-3 flex items-center gap-2">
408
+ <GitMerge className="h-4 w-4" />
409
+ Choisissez les valeurs pour chaque champ
410
+ </div>
411
+
412
+ <div className="space-y-3">
413
+ {allKeys.map((key) => {
414
+ const localValue = (localData as Record<string, unknown>)?.[key]
415
+ const serverValue = (serverData as Record<string, unknown>)?.[key]
416
+ const currentValue = (mergedData as Record<string, unknown>)?.[key]
417
+ const isLocalSelected = currentValue === localValue && localValue !== serverValue
418
+ const isServerSelected = currentValue === serverValue && localValue !== serverValue
419
+
420
+ return (
421
+ <div key={key} className="space-y-1">
422
+ <div className="text-xs font-medium text-muted-foreground">
423
+ {keyLabels?.[key] || key}
424
+ </div>
425
+ <div className="grid grid-cols-[1fr_auto_1fr] gap-2 items-center">
426
+ <button
427
+ type="button"
428
+ onClick={() => handleFieldChange(key, "local")}
429
+ className={cn(
430
+ "p-2 rounded border text-left text-sm truncate",
431
+ isLocalSelected
432
+ ? "border-blue-500 bg-blue-50 dark:bg-blue-950/50"
433
+ : "border-border hover:border-blue-300"
434
+ )}
435
+ >
436
+ {formatValue(localValue)}
437
+ </button>
438
+ <ArrowRight className="h-4 w-4 text-muted-foreground" />
439
+ <button
440
+ type="button"
441
+ onClick={() => handleFieldChange(key, "server")}
442
+ className={cn(
443
+ "p-2 rounded border text-left text-sm truncate",
444
+ isServerSelected
445
+ ? "border-green-500 bg-green-50 dark:bg-green-950/50"
446
+ : "border-border hover:border-green-300"
447
+ )}
448
+ >
449
+ {formatValue(serverValue)}
450
+ </button>
451
+ </div>
452
+ </div>
453
+ )
454
+ })}
455
+ </div>
456
+ </div>
457
+ )
458
+ }
459
+
460
+ /**
461
+ * Formate une valeur pour l'affichage
462
+ */
463
+ function formatValue(value: unknown): string {
464
+ if (value === null) return "null"
465
+ if (value === undefined) return "undefined"
466
+ if (typeof value === "object") {
467
+ try {
468
+ return JSON.stringify(value)
469
+ } catch {
470
+ return "[Object]"
471
+ }
472
+ }
473
+ return String(value)
474
+ }
475
+
476
+ DataTableConflictResolver.displayName = "DataTableConflictResolver"
477
+
478
+ export default DataTableConflictResolver
@@ -0,0 +1,219 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ ContextMenu,
6
+ ContextMenuContent,
7
+ ContextMenuItem,
8
+ ContextMenuSeparator,
9
+ ContextMenuTrigger,
10
+ } from "../context-menu"
11
+ import { Copy, Download, Edit, Trash2, Eye, EyeOff } from "lucide-react"
12
+ import { cn } from "./utils"
13
+
14
+ /**
15
+ * Props pour le menu contextuel du DataTable
16
+ */
17
+ export interface DataTableContextMenuProps<TData> {
18
+ /** Données de la ligne */
19
+ row: TData
20
+ /** Index de la ligne */
21
+ rowIndex: number
22
+ /** Actions disponibles */
23
+ actions?: Array<{
24
+ id: string
25
+ label: string
26
+ icon?: React.ReactNode
27
+ onClick: (row: TData) => void
28
+ disabled?: boolean
29
+ destructive?: boolean
30
+ }>
31
+ /** Actions de colonnes */
32
+ columnActions?: Array<{
33
+ id: string
34
+ label: string
35
+ icon?: React.ReactNode
36
+ onClick: (columnId: string) => void
37
+ disabled?: boolean
38
+ }>
39
+ /** Callback de copie */
40
+ onCopy?: (row: TData) => void
41
+ /** Callback d'export */
42
+ onExport?: (row: TData) => void
43
+ /** Callback d'édition */
44
+ onEdit?: (row: TData) => void
45
+ /** Callback de suppression */
46
+ onDelete?: (row: TData) => void
47
+ /** Callback de visibilité des colonnes */
48
+ onToggleColumnVisibility?: (columnId: string) => void
49
+ /** Colonnes visibles */
50
+ visibleColumns?: string[]
51
+ /** Classe CSS personnalisée */
52
+ className?: string
53
+ /** Enfants à wrapper */
54
+ children: React.ReactNode
55
+ }
56
+
57
+ /**
58
+ * Composant de menu contextuel pour le DataTable
59
+ */
60
+ export function DataTableContextMenu<TData>({
61
+ row,
62
+ rowIndex,
63
+ actions = [],
64
+ columnActions = [],
65
+ onCopy,
66
+ onExport,
67
+ onEdit,
68
+ onDelete,
69
+ onToggleColumnVisibility,
70
+ visibleColumns = [],
71
+ className,
72
+ children,
73
+ }: DataTableContextMenuProps<TData>) {
74
+ const [isOpen, setIsOpen] = React.useState(false)
75
+
76
+ const handleAction = React.useCallback((action: any) => {
77
+ action.onClick(row)
78
+ setIsOpen(false)
79
+ }, [row])
80
+
81
+ const handleCopy = React.useCallback(() => {
82
+ if (onCopy) {
83
+ onCopy(row)
84
+ } else {
85
+ // Copie par défaut des données de la ligne
86
+ const text = Object.entries(row as any)
87
+ .map(([key, value]) => `${key}: ${value}`)
88
+ .join("\n")
89
+ navigator.clipboard.writeText(text)
90
+ }
91
+ setIsOpen(false)
92
+ }, [row, onCopy])
93
+
94
+ const handleExport = React.useCallback(() => {
95
+ if (onExport) {
96
+ onExport(row)
97
+ }
98
+ setIsOpen(false)
99
+ }, [row, onExport])
100
+
101
+ const handleEdit = React.useCallback(() => {
102
+ if (onEdit) {
103
+ onEdit(row)
104
+ }
105
+ setIsOpen(false)
106
+ }, [row, onEdit])
107
+
108
+ const handleDelete = React.useCallback(() => {
109
+ if (onDelete) {
110
+ onDelete(row)
111
+ }
112
+ setIsOpen(false)
113
+ }, [row, onDelete])
114
+
115
+ const handleToggleColumn = React.useCallback((columnId: string) => {
116
+ if (onToggleColumnVisibility) {
117
+ onToggleColumnVisibility(columnId)
118
+ }
119
+ setIsOpen(false)
120
+ }, [onToggleColumnVisibility])
121
+
122
+ return (
123
+ <ContextMenu onOpenChange={setIsOpen}>
124
+ <ContextMenuTrigger asChild>
125
+ {children}
126
+ </ContextMenuTrigger>
127
+ <ContextMenuContent className={cn("w-64", className)}>
128
+ {/* Actions de ligne */}
129
+ {actions.length > 0 && (
130
+ <>
131
+ {actions.map((action) => (
132
+ <ContextMenuItem
133
+ key={action.id}
134
+ onClick={() => handleAction(action)}
135
+ disabled={action.disabled}
136
+ className={cn(
137
+ action.destructive && "text-destructive focus:text-destructive"
138
+ )}
139
+ >
140
+ {action.icon && <span className="mr-2">{action.icon}</span>}
141
+ {action.label}
142
+ </ContextMenuItem>
143
+ ))}
144
+ <ContextMenuSeparator />
145
+ </>
146
+ )}
147
+
148
+ {/* Actions par défaut */}
149
+ <ContextMenuItem onClick={handleCopy}>
150
+ <Copy className="mr-2 h-4 w-4" />
151
+ Copy row
152
+ </ContextMenuItem>
153
+
154
+ {onExport && (
155
+ <ContextMenuItem onClick={handleExport}>
156
+ <Download className="mr-2 h-4 w-4" />
157
+ Export row
158
+ </ContextMenuItem>
159
+ )}
160
+
161
+ {onEdit && (
162
+ <ContextMenuItem onClick={handleEdit}>
163
+ <Edit className="mr-2 h-4 w-4" />
164
+ Edit row
165
+ </ContextMenuItem>
166
+ )}
167
+
168
+ {onDelete && (
169
+ <ContextMenuItem
170
+ onClick={handleDelete}
171
+ className="text-destructive focus:text-destructive"
172
+ >
173
+ <Trash2 className="mr-2 h-4 w-4" />
174
+ Delete row
175
+ </ContextMenuItem>
176
+ )}
177
+
178
+ {/* Actions de colonnes */}
179
+ {columnActions.length > 0 && (
180
+ <>
181
+ <ContextMenuSeparator />
182
+ <div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground">
183
+ Columns
184
+ </div>
185
+ {columnActions.map((action) => (
186
+ <ContextMenuItem
187
+ key={action.id}
188
+ onClick={() => handleToggleColumn(action.id)}
189
+ disabled={action.disabled}
190
+ >
191
+ {action.icon && <span className="mr-2">{action.icon}</span>}
192
+ {action.label}
193
+ </ContextMenuItem>
194
+ ))}
195
+ </>
196
+ )}
197
+
198
+ {/* Toggle de visibilité des colonnes */}
199
+ {onToggleColumnVisibility && visibleColumns.length > 0 && (
200
+ <>
201
+ <ContextMenuSeparator />
202
+ <div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground">
203
+ Toggle Columns
204
+ </div>
205
+ {visibleColumns.map((columnId) => (
206
+ <ContextMenuItem
207
+ key={columnId}
208
+ onClick={() => handleToggleColumn(columnId)}
209
+ >
210
+ <Eye className="mr-2 h-4 w-4" />
211
+ {columnId}
212
+ </ContextMenuItem>
213
+ ))}
214
+ </>
215
+ )}
216
+ </ContextMenuContent>
217
+ </ContextMenu>
218
+ )
219
+ }