@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.
- package/README.md +71 -8
- package/dist/cli/commands/add.d.ts +7 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/list.d.ts +5 -0
- package/dist/cli/commands/search.d.ts +1 -0
- package/dist/cli/index.cjs +6014 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/utils/config.d.ts +29 -0
- package/dist/cli/utils/logger.d.ts +20 -0
- package/dist/cli/utils/registry.d.ts +23 -0
- package/package.json +14 -3
- package/src/blocks/activity-timeline/index.tsx +586 -0
- package/src/blocks/calendar-view/index.tsx +756 -0
- package/src/blocks/chat/index.tsx +1018 -0
- package/src/blocks/chat/widget.tsx +504 -0
- package/src/blocks/dashboard/index.tsx +522 -0
- package/src/blocks/empty-states/index.tsx +452 -0
- package/src/blocks/error-pages/index.tsx +426 -0
- package/src/blocks/faq/index.tsx +479 -0
- package/src/blocks/file-manager/index.tsx +890 -0
- package/src/blocks/footer/index.tsx +133 -0
- package/src/blocks/header/index.tsx +357 -0
- package/src/blocks/headtab/index.tsx +139 -0
- package/src/blocks/i18n-editor/index.tsx +1016 -0
- package/src/blocks/index.ts +80 -0
- package/src/blocks/kanban-board/index.tsx +779 -0
- package/src/blocks/landing/index.tsx +677 -0
- package/src/blocks/language-selector/index.tsx +88 -0
- package/src/blocks/layout/index.tsx +159 -0
- package/src/blocks/login/index.tsx +339 -0
- package/src/blocks/login/types.ts +131 -0
- package/src/blocks/pricing/index.tsx +564 -0
- package/src/blocks/profile/index.tsx +746 -0
- package/src/blocks/settings/index.tsx +558 -0
- package/src/blocks/sidebar/index.tsx +713 -0
- package/src/blocks/theme-creator-block/index.tsx +835 -0
- package/src/blocks/user-management/index.tsx +1037 -0
- package/src/blocks/wizard/index.tsx +719 -0
- package/src/components/DataTable/DataTable.tsx +406 -0
- package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
- package/src/components/DataTable/DataTableBody.tsx +216 -0
- package/src/components/DataTable/DataTableCell.tsx +172 -0
- package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
- package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
- package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
- package/src/components/DataTable/DataTableEditCell.tsx +279 -0
- package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
- package/src/components/DataTable/DataTableFilters.tsx +535 -0
- package/src/components/DataTable/DataTableGrouping.tsx +147 -0
- package/src/components/DataTable/DataTableHeader.tsx +172 -0
- package/src/components/DataTable/DataTablePagination.tsx +125 -0
- package/src/components/DataTable/DataTableSelection.tsx +269 -0
- package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
- package/src/components/DataTable/DataTableToolbar.tsx +262 -0
- package/src/components/DataTable/README.md +446 -0
- package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
- package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
- package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
- package/src/components/DataTable/examples/EditExample.tsx +166 -0
- package/src/components/DataTable/formatters/index.ts +335 -0
- package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
- package/src/components/DataTable/hooks/useDataTable.ts +145 -0
- package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
- package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
- package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
- package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
- package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
- package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
- package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
- package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
- package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
- package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
- package/src/components/DataTable/index.ts +81 -0
- package/src/components/DataTable/services/IndexedDBService.ts +504 -0
- package/src/components/DataTable/templates/index.tsx +803 -0
- package/src/components/DataTable/types.ts +504 -0
- package/src/components/DataTable/utils.ts +164 -0
- package/src/components/DataTable/workers/exportWorker.ts +213 -0
- package/src/components/accordion/index.tsx +61 -0
- package/src/components/alert/index.tsx +61 -0
- package/src/components/alert-dialog/index.tsx +146 -0
- package/src/components/aspect-ratio/index.tsx +12 -0
- package/src/components/avatar/index.tsx +54 -0
- package/src/components/badge/Badge.stories.tsx +64 -0
- package/src/components/badge/index.tsx +38 -0
- package/src/components/button/Button.stories.tsx +173 -0
- package/src/components/button/index.tsx +56 -0
- package/src/components/calendar/index.tsx +73 -0
- package/src/components/card/index.tsx +78 -0
- package/src/components/checkbox/index.tsx +34 -0
- package/src/components/code/index.tsx +229 -0
- package/src/components/collapsible/index.tsx +16 -0
- package/src/components/command/index.tsx +162 -0
- package/src/components/context-menu/index.tsx +204 -0
- package/src/components/dialog/index.tsx +126 -0
- package/src/components/dropdown-menu/index.tsx +204 -0
- package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
- package/src/components/error-boundary/index.ts +7 -0
- package/src/components/form/index.tsx +183 -0
- package/src/components/hover-card/index.tsx +33 -0
- package/src/components/index.ts +368 -0
- package/src/components/input/Input.stories.tsx +100 -0
- package/src/components/input/index.tsx +27 -0
- package/src/components/input-otp/index.tsx +277 -0
- package/src/components/label/index.tsx +30 -0
- package/src/components/language-selector/index.tsx +341 -0
- package/src/components/menubar/index.tsx +240 -0
- package/src/components/navigation-menu/index.tsx +134 -0
- package/src/components/popover/index.tsx +35 -0
- package/src/components/progress/index.tsx +32 -0
- package/src/components/radio-group/index.tsx +48 -0
- package/src/components/scroll-area/index.tsx +52 -0
- package/src/components/select/index.tsx +164 -0
- package/src/components/separator/index.tsx +35 -0
- package/src/components/sheet/index.tsx +147 -0
- package/src/components/skeleton/index.tsx +22 -0
- package/src/components/slider/index.tsx +32 -0
- package/src/components/switch/index.tsx +33 -0
- package/src/components/table/index.tsx +117 -0
- package/src/components/tabs/index.tsx +59 -0
- package/src/components/textarea/index.tsx +30 -0
- package/src/components/theme-selector/index.tsx +327 -0
- package/src/components/toast/index.tsx +133 -0
- package/src/components/toaster/index.tsx +34 -0
- package/src/components/toggle/index.tsx +49 -0
- package/src/components/tooltip/index.tsx +34 -0
- package/src/components/typography/index.tsx +276 -0
- package/src/components/waka-3d-pie-chart/index.tsx +486 -0
- package/src/components/waka-achievement-unlock/index.tsx +716 -0
- package/src/components/waka-activity-feed/index.tsx +686 -0
- package/src/components/waka-address-autocomplete/index.tsx +1202 -0
- package/src/components/waka-admincrumb/index.tsx +349 -0
- package/src/components/waka-alert-stack/index.tsx +827 -0
- package/src/components/waka-allocation-matrix/index.tsx +1278 -0
- package/src/components/waka-approval-chain/index.tsx +766 -0
- package/src/components/waka-audit-log/index.tsx +1475 -0
- package/src/components/waka-autocomplete/index.tsx +358 -0
- package/src/components/waka-badge-showcase/index.tsx +704 -0
- package/src/components/waka-barcode/index.tsx +260 -0
- package/src/components/waka-biometric-prompt/index.tsx +765 -0
- package/src/components/waka-bottom-sheet/index.tsx +495 -0
- package/src/components/waka-breadcrumb/index.tsx +376 -0
- package/src/components/waka-breadcrumb-path/index.tsx +513 -0
- package/src/components/waka-budget-burn/index.tsx +1234 -0
- package/src/components/waka-capacity-planner/index.tsx +1107 -0
- package/src/components/waka-carousel/index.tsx +893 -0
- package/src/components/waka-cart-summary/index.tsx +1055 -0
- package/src/components/waka-challenge-timer/index.tsx +1044 -0
- package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
- package/src/components/waka-charts/WakaBarChart.tsx +222 -0
- package/src/components/waka-charts/WakaChart.tsx +124 -0
- package/src/components/waka-charts/WakaLineChart.tsx +219 -0
- package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
- package/src/components/waka-charts/WakaPieChart.tsx +214 -0
- package/src/components/waka-charts/WakaSparkline.tsx +229 -0
- package/src/components/waka-charts/dataTableHelpers.ts +109 -0
- package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
- package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
- package/src/components/waka-charts/index.ts +90 -0
- package/src/components/waka-charts/types.ts +330 -0
- package/src/components/waka-chat-bubble/index.tsx +1060 -0
- package/src/components/waka-checklist/index.tsx +1067 -0
- package/src/components/waka-checkout-stepper/index.tsx +976 -0
- package/src/components/waka-cohort-table/index.tsx +1011 -0
- package/src/components/waka-color-picker/index.tsx +447 -0
- package/src/components/waka-combo-counter/index.tsx +864 -0
- package/src/components/waka-combobox/index.tsx +497 -0
- package/src/components/waka-command-bar/index.tsx +403 -0
- package/src/components/waka-compare-period/index.tsx +1230 -0
- package/src/components/waka-connection-matrix/index.tsx +1053 -0
- package/src/components/waka-contribution-graph/index.tsx +552 -0
- package/src/components/waka-cost-breakdown/index.tsx +1065 -0
- package/src/components/waka-coupon-input/index.tsx +592 -0
- package/src/components/waka-credit-card-input/index.tsx +982 -0
- package/src/components/waka-daily-reward/index.tsx +762 -0
- package/src/components/waka-date-range-picker/index.tsx +378 -0
- package/src/components/waka-datetime-picker/index.tsx +793 -0
- package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
- package/src/components/waka-deployment-lane/index.tsx +673 -0
- package/src/components/waka-device-trust/index.tsx +1259 -0
- package/src/components/waka-dock/index.tsx +285 -0
- package/src/components/waka-drawer/index.tsx +319 -0
- package/src/components/waka-empty-state/index.tsx +545 -0
- package/src/components/waka-error-shake/index.tsx +398 -0
- package/src/components/waka-feature-announcement/index.tsx +991 -0
- package/src/components/waka-file-upload/index.tsx +437 -0
- package/src/components/waka-floating-nav/index.tsx +413 -0
- package/src/components/waka-flow-diagram/index.tsx +508 -0
- package/src/components/waka-funnel-chart/index.tsx +823 -0
- package/src/components/waka-glow-card/index.tsx +246 -0
- package/src/components/waka-goal-progress/index.tsx +1025 -0
- package/src/components/waka-haptic-button/index.tsx +388 -0
- package/src/components/waka-health-pulse/index.tsx +451 -0
- package/src/components/waka-heatmap/index.tsx +1026 -0
- package/src/components/waka-hotspot/index.tsx +682 -0
- package/src/components/waka-image/index.tsx +373 -0
- package/src/components/waka-incident-timeline/index.tsx +686 -0
- package/src/components/waka-invoice-preview/index.tsx +829 -0
- package/src/components/waka-kanban/index.tsx +646 -0
- package/src/components/waka-kpi-dashboard/index.tsx +755 -0
- package/src/components/waka-leaderboard/index.tsx +746 -0
- package/src/components/waka-level-progress/index.tsx +665 -0
- package/src/components/waka-liquid-button/index.tsx +520 -0
- package/src/components/waka-loading-orbit/index.tsx +478 -0
- package/src/components/waka-loot-box/index.tsx +1091 -0
- package/src/components/waka-magic-link/index.tsx +321 -0
- package/src/components/waka-magnetic-button/index.tsx +567 -0
- package/src/components/waka-mention-input/index.tsx +953 -0
- package/src/components/waka-metric-sparkline/index.tsx +627 -0
- package/src/components/waka-milestone-road/index.tsx +1064 -0
- package/src/components/waka-modal/index.tsx +374 -0
- package/src/components/waka-morph-button/index.tsx +495 -0
- package/src/components/waka-network-topology/index.tsx +801 -0
- package/src/components/waka-notifications/index.tsx +414 -0
- package/src/components/waka-number-input/index.tsx +373 -0
- package/src/components/waka-orbital-menu/index.tsx +445 -0
- package/src/components/waka-order-tracker/index.tsx +1041 -0
- package/src/components/waka-pagination/index.tsx +393 -0
- package/src/components/waka-password-strength/index.tsx +824 -0
- package/src/components/waka-payment-method-picker/index.tsx +715 -0
- package/src/components/waka-permission-matrix/index.tsx +1302 -0
- package/src/components/waka-phone-input/index.tsx +801 -0
- package/src/components/waka-pipeline-view/index.tsx +604 -0
- package/src/components/waka-player-card/index.tsx +691 -0
- package/src/components/waka-points-popup/index.tsx +366 -0
- package/src/components/waka-power-up/index.tsx +1155 -0
- package/src/components/waka-presence-indicator/index.tsx +1181 -0
- package/src/components/waka-pricing-table/index.tsx +755 -0
- package/src/components/waka-product-card/index.tsx +786 -0
- package/src/components/waka-progress-onboarding/index.tsx +878 -0
- package/src/components/waka-pull-to-refresh/index.tsx +451 -0
- package/src/components/waka-qrcode/index.tsx +232 -0
- package/src/components/waka-quest-card/index.tsx +1275 -0
- package/src/components/waka-quota-bar/index.tsx +693 -0
- package/src/components/waka-radar-score/index.tsx +512 -0
- package/src/components/waka-rank-badge/index.tsx +813 -0
- package/src/components/waka-rating-input/index.tsx +560 -0
- package/src/components/waka-reaction-picker/index.tsx +1062 -0
- package/src/components/waka-region-map/index.tsx +730 -0
- package/src/components/waka-resource-gauge/index.tsx +654 -0
- package/src/components/waka-resource-pool/index.tsx +1035 -0
- package/src/components/waka-rich-text-editor/index.tsx +594 -0
- package/src/components/waka-rollback-slider/index.tsx +891 -0
- package/src/components/waka-sankey-diagram/index.tsx +1032 -0
- package/src/components/waka-schedule-picker/index.tsx +1060 -0
- package/src/components/waka-scratch-card/index.tsx +914 -0
- package/src/components/waka-season-pass/index.tsx +886 -0
- package/src/components/waka-security-score/index.tsx +1126 -0
- package/src/components/waka-segmented-control/index.tsx +238 -0
- package/src/components/waka-server-rack/index.tsx +764 -0
- package/src/components/waka-session-manager/index.tsx +815 -0
- package/src/components/waka-signature-pad/index.tsx +744 -0
- package/src/components/waka-skeleton-wave/index.tsx +454 -0
- package/src/components/waka-skill-tree/index.tsx +1031 -0
- package/src/components/waka-sla-tracker/index.tsx +798 -0
- package/src/components/waka-slider-range/index.tsx +765 -0
- package/src/components/waka-spin-wheel/index.tsx +671 -0
- package/src/components/waka-spinner/index.tsx +284 -0
- package/src/components/waka-spotlight/index.tsx +410 -0
- package/src/components/waka-stat/index.tsx +428 -0
- package/src/components/waka-stats-hexagon/index.tsx +824 -0
- package/src/components/waka-status-matrix/index.tsx +565 -0
- package/src/components/waka-stepper/index.tsx +489 -0
- package/src/components/waka-streak-counter/index.tsx +334 -0
- package/src/components/waka-success-explosion/index.tsx +453 -0
- package/src/components/waka-swipe-card/index.tsx +574 -0
- package/src/components/waka-tabs-morph/index.tsx +509 -0
- package/src/components/waka-tag-input/index.tsx +877 -0
- package/src/components/waka-team-banner/index.tsx +1183 -0
- package/src/components/waka-terminal-output/index.tsx +836 -0
- package/src/components/waka-theme-creator/index.tsx +762 -0
- package/src/components/waka-theme-manager/index.tsx +654 -0
- package/src/components/waka-thread-view/index.tsx +874 -0
- package/src/components/waka-tilt-card/index.tsx +250 -0
- package/src/components/waka-time-picker/index.tsx +479 -0
- package/src/components/waka-timeline/index.tsx +385 -0
- package/src/components/waka-tooltip-tour/index.tsx +855 -0
- package/src/components/waka-tour-guide/index.tsx +920 -0
- package/src/components/waka-tournament-bracket/index.tsx +1276 -0
- package/src/components/waka-tree/index.tsx +557 -0
- package/src/components/waka-treemap-chart/index.tsx +1031 -0
- package/src/components/waka-two-factor-setup/index.tsx +995 -0
- package/src/components/waka-typewriter/index.tsx +566 -0
- package/src/components/waka-typing-indicator/index.tsx +649 -0
- package/src/components/waka-versus-card/index.tsx +1026 -0
- package/src/components/waka-video/index.tsx +557 -0
- package/src/components/waka-video-call/index.tsx +1087 -0
- package/src/components/waka-virtual-list/index.tsx +327 -0
- package/src/components/waka-voice-message/index.tsx +1019 -0
- package/src/components/waka-welcome-modal/index.tsx +790 -0
- 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
|
+
}
|