diff --git a/visual_workflow_builder/frontend_v4/src/App.tsx b/visual_workflow_builder/frontend_v4/src/App.tsx index 5aa1aa019..86731b10a 100644 --- a/visual_workflow_builder/frontend_v4/src/App.tsx +++ b/visual_workflow_builder/frontend_v4/src/App.tsx @@ -14,19 +14,16 @@ import '@xyflow/react/dist/style.css'; import * as api from './services/api'; import type { AppState, Step, ActionType, Capture, ExecutionMode } from './types'; -import { ACTIONS, EXECUTION_MODES } from './types'; +// Types importés via les sous-composants import StepNode from './components/StepNode'; import ToolPalette from './components/ToolPalette'; -import PropertiesPanel from './components/PropertiesPanel'; -import CapturePanel from './components/CapturePanel'; import WorkflowSelector from './components/WorkflowSelector'; import WorkflowManagerModal from './components/WorkflowManagerModal'; import ExecutionControls from './components/ExecutionControls'; import ExecutionModeToggle from './components/ExecutionModeToggle'; import ExecutionOverlay from './components/ExecutionOverlay'; -import VariableManager from './components/VariableManager'; import type { Variable } from './components/VariableManager'; -import CaptureLibrary from './components/CaptureLibrary'; +import RightPanel from './components/RightPanel'; import SelfHealingDialog from './components/SelfHealingDialog'; import ConfidenceDashboard from './components/ConfidenceDashboard'; import WorkflowValidation from './components/WorkflowValidation'; @@ -494,42 +491,27 @@ function App() { )} - {/* Sidebar droite: Propriétés + Capture + Variables */} - - - - {/* Indicateur de mode flottant */} -
- {EXECUTION_MODES[executionMode].icon} - Mode {EXECUTION_MODES[executionMode].label} + {/* Sidebar droite: Panneau à onglets (Propriétés / Capture / Données) */} +
{/* Overlay de debug en temps réel */} diff --git a/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx b/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx index 593e869fd..b69a8b2d9 100644 --- a/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx +++ b/visual_workflow_builder/frontend_v4/src/components/CapturePanel.tsx @@ -35,6 +35,7 @@ export default function CapturePanel({ onSetDetectionZone }: Props) { const [isFullscreen, setIsFullscreen] = useState(false); + const [showLibraryGallery, setShowLibraryGallery] = useState(false); const [library, setLibrary] = useState([]); const [currentCapture, setCurrentCapture] = useState(null); const [timerSeconds, setTimerSeconds] = useState(0); @@ -133,6 +134,7 @@ export default function CapturePanel({ const handleLibrarySelect = (item: LibraryItem) => { setCurrentCapture(item.capture); + setIsFullscreen(true); // Ouvrir en plein écran pour sélectionner }; const handleDeleteLibraryItem = (id: string) => { @@ -220,9 +222,12 @@ export default function CapturePanel({ {/* Bibliothèque */}
-

Bibliothèque ({library.length})

+

library.length > 0 && setShowLibraryGallery(true)}> + Bibliothèque ({library.length}) + {library.length > 0 && ▸ Ouvrir} +

- {library.map(item => ( + {library.slice(0, 4).map(item => (
))} + {library.length > 4 && ( + + )}
+ {/* Galerie plein écran de la bibliothèque */} + {showLibraryGallery && ( +
+
+ Bibliothèque — {library.length} captures — Cliquez pour sélectionner + +
+
+ {library.map(item => ( +
{ + setShowLibraryGallery(false); + handleLibrarySelect(item); + }} + > + Capture +
+ {new Date(item.timestamp).toLocaleTimeString('fr-FR')} +
+
+ ))} +
+
+ )} + {/* Modal plein écran */} {isFullscreen && currentCapture && ( ) => void; + onDeleteStep: (id: string) => void; + // Capture + capture: Capture | null; + onCapture: () => void; + onSelectAnchor: (bbox: { x: number; y: number; width: number; height: number }, screenshotBase64?: string) => void; + hasSelectedStep: boolean; + executionMode: ExecutionMode; + detectionZone: { x: number; y: number; width: number; height: number } | null; + onSetDetectionZone: (zone: { x: number; y: number; width: number; height: number } | null) => void; + // Bibliothèque + currentCapture: Capture | null; + onSelectCaptureFromLibrary: (capture: Capture) => void; + // Variables + variables: Variable[]; + onVariableCreate: (data: Omit) => void; + onVariableUpdate: (id: string, data: Partial) => void; + onVariableDelete: (id: string) => void; + steps: Step[]; + runtimeVariables: Record; +} + +export default function RightPanel({ + selectedStep, + onUpdateStepParams, + onDeleteStep, + capture, + onCapture, + onSelectAnchor, + hasSelectedStep, + executionMode, + detectionZone, + onSetDetectionZone, + currentCapture, + onSelectCaptureFromLibrary, + variables, + onVariableCreate, + onVariableUpdate, + onVariableDelete, + steps, + runtimeVariables, +}: Props) { + const [activeTab, setActiveTab] = useState('properties'); + const [isExpanded, setIsExpanded] = useState(false); + const panelRef = useRef(null); + + // Basculer sur l'onglet Propriétés quand une étape est sélectionnée + useEffect(() => { + if (selectedStep) { + setActiveTab('properties'); + setIsExpanded(true); + } + }, [selectedStep?.id]); + + // Basculer sur l'onglet Capture quand une nouvelle capture arrive + useEffect(() => { + if (capture) { + setActiveTab('capture'); + setIsExpanded(true); + } + }, [capture]); + + // Clic en dehors du panneau pour replier + const handleClickOutside = useCallback((e: MouseEvent) => { + if (panelRef.current && !panelRef.current.contains(e.target as Node)) { + setIsExpanded(false); + } + }, []); + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [handleClickOutside]); + + // Clic sur un onglet + const handleTabClick = (tabId: TabId) => { + if (activeTab === tabId && isExpanded) { + // Re-clic sur le même onglet : replier + setIsExpanded(false); + } else { + setActiveTab(tabId); + setIsExpanded(true); + } + }; + + // Rendu du contenu de l'onglet actif + const renderTabContent = () => { + switch (activeTab) { + case 'properties': + return ( +
+ +
+ ); + + case 'capture': + return ( +
+ +
+ ); + + case 'data': + return ( +
+ + +
+ ); + + default: + return null; + } + }; + + return ( + + ); +} diff --git a/visual_workflow_builder/frontend_v4/src/components/StepNode.tsx b/visual_workflow_builder/frontend_v4/src/components/StepNode.tsx index 4829415a8..0ddc0a9fa 100644 --- a/visual_workflow_builder/frontend_v4/src/components/StepNode.tsx +++ b/visual_workflow_builder/frontend_v4/src/components/StepNode.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import { memo, useState, useEffect, useRef } from 'react'; import { Handle, Position } from '@xyflow/react'; import type { Step } from '../types'; @@ -34,8 +35,8 @@ function StepNode({ data, selected }: StepNodeProps) { return (
- {/* Bouton aide (?) — toujours visible quand sélectionné */} - {selected && action && ( + {/* Bouton aide (?) */} + {action && (