Fix: Navigation Executor + liste scrollable + sauvegarde variables
- Ajout onglets Standard/VWB dans Executor pour permettre la navigation - Liste d'exécution scrollable (max 300px) - Synchronisation bidirectionnelle des variables avec le workflow Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
630
visual_workflow_builder/frontend/src/App.tsx
Normal file
630
visual_workflow_builder/frontend/src/App.tsx
Normal file
@@ -0,0 +1,630 @@
|
||||
/**
|
||||
* Application principale Visual Workflow Builder V2
|
||||
* Auteur : Dom, Alice, Kiro - 09 janvier 2026
|
||||
*
|
||||
* Interface moderne pour la création de workflows d'automatisation RPA
|
||||
* avec sélection visuelle basée sur la vision et terminologie française.
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useMemo, useEffect, Component, ErrorInfo, ReactNode } from 'react';
|
||||
import {
|
||||
Box,
|
||||
CssBaseline,
|
||||
ThemeProvider,
|
||||
createTheme,
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Tabs,
|
||||
Tab,
|
||||
Drawer,
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Button,
|
||||
} from '@mui/material';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
import { store } from './store';
|
||||
|
||||
// Composants principaux
|
||||
import Canvas from './components/Canvas';
|
||||
import Palette from './components/Palette';
|
||||
import PropertiesPanel from './components/PropertiesPanel';
|
||||
import VariableManager from './components/VariableManager';
|
||||
import DocumentationTab from './components/DocumentationTab';
|
||||
import Validator from './components/Validator';
|
||||
import Executor from './components/Executor';
|
||||
import WorkflowManager from './components/WorkflowManager';
|
||||
import ContextualHelp from './components/ContextualHelp';
|
||||
import KeyboardShortcuts from './components/KeyboardShortcuts';
|
||||
import AccessibilityProvider from './components/AccessibilityProvider';
|
||||
import ConnectionIndicator from './components/ConnectionIndicator';
|
||||
import TestCatalogLoader from './components/TestCatalogLoader';
|
||||
import VWBIntegrationTest from './components/VWBIntegrationTest';
|
||||
|
||||
// Hooks personnalisés
|
||||
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
||||
import { useResponsiveLayout } from './hooks/useResponsiveLayout';
|
||||
|
||||
// Import des types partagés
|
||||
import {
|
||||
Step,
|
||||
Variable,
|
||||
StepTemplate,
|
||||
StepCategory,
|
||||
Position,
|
||||
Workflow,
|
||||
WorkflowConnection,
|
||||
StepExecutionState,
|
||||
} from './types';
|
||||
|
||||
// Error Boundary pour gérer les erreurs de l'application
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
errorInfo?: ErrorInfo;
|
||||
}
|
||||
|
||||
class AppErrorBoundary extends Component<
|
||||
{ children: ReactNode },
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
constructor(props: { children: ReactNode }) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
// Met à jour l'état pour afficher l'interface d'erreur
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// Log l'erreur pour le debugging
|
||||
console.error('Erreur capturée par Error Boundary:', error, errorInfo);
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '100vh',
|
||||
p: 3,
|
||||
}}
|
||||
>
|
||||
<Alert severity="error" sx={{ maxWidth: 600, mb: 2 }}>
|
||||
<AlertTitle>Une erreur inattendue s'est produite</AlertTitle>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
L'application a rencontré une erreur et ne peut pas continuer.
|
||||
</Typography>
|
||||
{this.state.error && (
|
||||
<Typography variant="caption" component="pre" sx={{ mt: 1 }}>
|
||||
{this.state.error.message}
|
||||
</Typography>
|
||||
)}
|
||||
</Alert>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => window.location.reload()}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
Recharger l'application
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
// Thème Material-UI personnalisé avec couleurs françaises - mémorisé pour éviter les re-créations
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#1976d2', // Bleu français
|
||||
},
|
||||
secondary: {
|
||||
main: '#dc004e', // Rouge français
|
||||
},
|
||||
background: {
|
||||
default: '#fafafa',
|
||||
paper: '#ffffff',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: '"Segoe UI", "Roboto", "Helvetica", "Arial", sans-serif',
|
||||
},
|
||||
components: {
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#ffffff',
|
||||
color: '#1976d2',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Composant principal de l'application
|
||||
*/
|
||||
function App() {
|
||||
// États principaux
|
||||
const [selectedStep, setSelectedStep] = useState<Step | null>(null);
|
||||
const [variables, setVariables] = useState<Variable[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [activeDocTab, setActiveDocTab] = useState(0);
|
||||
const [isDocDrawerOpen, setIsDocDrawerOpen] = useState(false);
|
||||
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false);
|
||||
|
||||
// Hooks d'accessibilité et responsivité
|
||||
const responsiveLayout = useResponsiveLayout();
|
||||
|
||||
// État du workflow
|
||||
const [workflow, setWorkflow] = useState<Workflow>({
|
||||
id: 'workflow_1',
|
||||
name: 'Nouveau Workflow',
|
||||
description: 'Workflow créé avec le Visual Workflow Builder V2',
|
||||
steps: [],
|
||||
connections: [],
|
||||
variables: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
// Configuration de la navigation au clavier
|
||||
const keyboardNavigation = useKeyboardNavigation({
|
||||
onStepSelect: (stepId: string) => {
|
||||
const step = workflow.steps.find(s => s.id === stepId);
|
||||
if (step) handleStepSelect(step);
|
||||
},
|
||||
onStepMove: (stepId: string, direction: 'up' | 'down' | 'left' | 'right') => {
|
||||
const step = workflow.steps.find(s => s.id === stepId);
|
||||
if (step) {
|
||||
const moveDistance = 20;
|
||||
const newPosition = { ...step.position };
|
||||
|
||||
switch (direction) {
|
||||
case 'up': newPosition.y -= moveDistance; break;
|
||||
case 'down': newPosition.y += moveDistance; break;
|
||||
case 'left': newPosition.x -= moveDistance; break;
|
||||
case 'right': newPosition.x += moveDistance; break;
|
||||
}
|
||||
|
||||
handleStepMove(stepId, newPosition);
|
||||
}
|
||||
},
|
||||
onStepDelete: (stepId: string) => handleStepDelete(stepId),
|
||||
onSave: () => console.log('Sauvegarde via clavier'),
|
||||
onHelp: () => setShowKeyboardShortcuts(true),
|
||||
selectedStepId: selectedStep?.id,
|
||||
availableStepIds: workflow.steps.map(s => s.id),
|
||||
isEnabled: true,
|
||||
});
|
||||
|
||||
// Gestionnaires d'événements optimisés avec useCallback
|
||||
const handleStepSelect = useCallback((step: Step | null) => {
|
||||
setSelectedStep(step);
|
||||
}, []);
|
||||
|
||||
// Synchroniser selectedStep avec le workflow quand celui-ci change
|
||||
// Ceci corrige le bug où les paramètres (visual_anchor) ne sont pas persistés dans l'UI
|
||||
useEffect(() => {
|
||||
if (selectedStep) {
|
||||
const updatedStep = workflow.steps.find(s => s.id === selectedStep.id);
|
||||
if (updatedStep) {
|
||||
// Vérifier si les données ont changé avant de mettre à jour
|
||||
const currentDataStr = JSON.stringify(selectedStep.data);
|
||||
const updatedDataStr = JSON.stringify(updatedStep.data);
|
||||
if (currentDataStr !== updatedDataStr) {
|
||||
setSelectedStep(updatedStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [workflow.steps, selectedStep?.id]);
|
||||
|
||||
const handleStepMove = useCallback((stepId: string, position: Position) => {
|
||||
console.log('Déplacement étape:', stepId, position);
|
||||
setWorkflow(prev => ({
|
||||
...prev,
|
||||
steps: prev.steps.map(step =>
|
||||
step.id === stepId ? { ...step, position } : step
|
||||
),
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleConnection = (source: string, target: string) => {
|
||||
console.log('Connexion:', source, '->', target);
|
||||
const newConnection: WorkflowConnection = {
|
||||
id: `conn_${Date.now()}`,
|
||||
source,
|
||||
target,
|
||||
};
|
||||
setWorkflow(prev => ({
|
||||
...prev,
|
||||
connections: [...prev.connections, newConnection],
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleStepAdd = (stepData: Omit<Step, 'id'>) => {
|
||||
console.log('Ajout étape:', stepData);
|
||||
const newStep: Step = {
|
||||
...stepData,
|
||||
id: `step_${Date.now()}`,
|
||||
executionState: StepExecutionState.IDLE,
|
||||
validationErrors: [],
|
||||
};
|
||||
|
||||
setWorkflow(prev => ({
|
||||
...prev,
|
||||
steps: [...prev.steps, newStep],
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleStepDelete = (stepId: string) => {
|
||||
console.log('Suppression étape:', stepId);
|
||||
setWorkflow(prev => ({
|
||||
...prev,
|
||||
steps: prev.steps.filter(step => step.id !== stepId),
|
||||
connections: prev.connections.filter(conn =>
|
||||
conn.source !== stepId && conn.target !== stepId
|
||||
),
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
|
||||
// Désélectionner l'étape si elle était sélectionnée
|
||||
if (selectedStep?.id === stepId) {
|
||||
setSelectedStep(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConnectionDelete = (connectionId: string) => {
|
||||
console.log('Suppression connexion:', connectionId);
|
||||
setWorkflow(prev => ({
|
||||
...prev,
|
||||
connections: prev.connections.filter(conn => conn.id !== connectionId),
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleParameterChange = (stepId: string, param: string, value: any) => {
|
||||
console.log('Changement paramètre:', stepId, param, value);
|
||||
setWorkflow(prev => ({
|
||||
...prev,
|
||||
steps: prev.steps.map(step =>
|
||||
step.id === stepId
|
||||
? {
|
||||
...step,
|
||||
data: {
|
||||
...step.data,
|
||||
parameters: {
|
||||
...step.data.parameters,
|
||||
[param]: value
|
||||
}
|
||||
}
|
||||
}
|
||||
: step
|
||||
),
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleVisualSelection = (stepId: string) => {
|
||||
console.log('Sélection visuelle pour étape:', stepId);
|
||||
// La sélection visuelle est maintenant gérée par le VisualSelector dans PropertiesPanel
|
||||
};
|
||||
|
||||
const handleStepHighlight = (stepId: string, highlight: boolean) => {
|
||||
// Mise en évidence des étapes pour la validation
|
||||
console.log('Mise en évidence étape:', stepId, highlight ? 'activée' : 'désactivée');
|
||||
};
|
||||
|
||||
const handleStepStateChange = (stepId: string, state: StepExecutionState) => {
|
||||
setWorkflow(prev => ({
|
||||
...prev,
|
||||
steps: prev.steps.map(step =>
|
||||
step.id === stepId ? { ...step, executionState: state } : step
|
||||
),
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleExecutionComplete = (success: boolean, summary: any) => {
|
||||
console.log('Exécution terminée:', success ? 'Succès' : 'Échec', summary);
|
||||
};
|
||||
|
||||
const handleWorkflowLoad = (loadedWorkflow: Workflow) => {
|
||||
console.log('📥 [App] Chargement workflow:', {
|
||||
name: loadedWorkflow.name,
|
||||
stepsCount: loadedWorkflow.steps?.length || 0,
|
||||
connectionsCount: loadedWorkflow.connections?.length || 0,
|
||||
steps: loadedWorkflow.steps,
|
||||
});
|
||||
|
||||
// S'assurer que les étapes ont des positions valides
|
||||
const workflowWithPositions: Workflow = {
|
||||
...loadedWorkflow,
|
||||
steps: (loadedWorkflow.steps || []).map((step, index) => ({
|
||||
...step,
|
||||
position: step.position || { x: 100 + (index % 3) * 200, y: 100 + Math.floor(index / 3) * 150 },
|
||||
})),
|
||||
};
|
||||
|
||||
setWorkflow(workflowWithPositions);
|
||||
// Charger les variables du workflow
|
||||
setVariables(workflowWithPositions.variables || []);
|
||||
setSelectedStep(null);
|
||||
console.log('✅ [App] Workflow appliqué avec', workflowWithPositions.steps.length, 'étapes et', (workflowWithPositions.variables || []).length, 'variables');
|
||||
};
|
||||
|
||||
const handleWorkflowSave = (savedWorkflow: Workflow) => {
|
||||
setWorkflow(savedWorkflow);
|
||||
console.log('Workflow sauvegardé:', savedWorkflow.name);
|
||||
};
|
||||
|
||||
const handleStepDrag = (stepTemplate: StepTemplate) => {
|
||||
console.log('Début drag étape:', stepTemplate);
|
||||
// Cette fonction est appelée quand le drag commence depuis la palette
|
||||
// La logique de drop est gérée dans le Canvas
|
||||
};
|
||||
|
||||
// Gestion des variables
|
||||
const handleVariableCreate = (variable: Omit<Variable, 'id'>) => {
|
||||
const newVariable: Variable = {
|
||||
...variable,
|
||||
id: `var_${Date.now()}`,
|
||||
};
|
||||
const updatedVariables = [...variables, newVariable];
|
||||
setVariables(updatedVariables);
|
||||
// Synchroniser avec le workflow pour la sauvegarde
|
||||
setWorkflow(prev => ({ ...prev, variables: updatedVariables, updatedAt: new Date() }));
|
||||
};
|
||||
|
||||
const handleVariableUpdate = (id: string, updates: Partial<Variable>) => {
|
||||
const updatedVariables = variables.map(v => v.id === id ? { ...v, ...updates } : v);
|
||||
setVariables(updatedVariables);
|
||||
// Synchroniser avec le workflow pour la sauvegarde
|
||||
setWorkflow(prev => ({ ...prev, variables: updatedVariables, updatedAt: new Date() }));
|
||||
};
|
||||
|
||||
const handleVariableDelete = (id: string) => {
|
||||
const updatedVariables = variables.filter(v => v.id !== id);
|
||||
setVariables(updatedVariables);
|
||||
// Synchroniser avec le workflow pour la sauvegarde
|
||||
setWorkflow(prev => ({ ...prev, variables: updatedVariables, updatedAt: new Date() }));
|
||||
};
|
||||
|
||||
// Onglets de documentation
|
||||
const docTabs = [
|
||||
{ id: 'canvas', label: 'Canvas', toolName: 'canvas' },
|
||||
{ id: 'palette', label: 'Palette', toolName: 'palette' },
|
||||
{ id: 'properties', label: 'Propriétés', toolName: 'properties' },
|
||||
{ id: 'test', label: 'Test Catalogue', toolName: 'test' },
|
||||
{ id: 'vwb-test', label: 'Test VWB', toolName: 'vwb-test' },
|
||||
];
|
||||
|
||||
// Mode 100% Visuel - Pas de catégories par défaut
|
||||
// Seules les actions VisionOnly du catalogue sont utilisées
|
||||
const defaultCategories: StepCategory[] = [];
|
||||
|
||||
return (
|
||||
<AppErrorBoundary>
|
||||
<AccessibilityProvider>
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ReactFlowProvider>
|
||||
<CssBaseline />
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
|
||||
{/* Barre d'application */}
|
||||
<AppBar position="static" elevation={1}>
|
||||
<Toolbar>
|
||||
<Typography variant="h6" component="h1" sx={{ flexGrow: 1 }}>
|
||||
Visual Workflow Builder V2
|
||||
</Typography>
|
||||
|
||||
{/* Gestionnaire de workflows */}
|
||||
<Box sx={{ mr: 2 }}>
|
||||
<WorkflowManager
|
||||
currentWorkflow={workflow}
|
||||
onWorkflowLoad={handleWorkflowLoad}
|
||||
onWorkflowSave={handleWorkflowSave}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Indicateur de connexion API */}
|
||||
<Box sx={{ mr: 2 }}>
|
||||
<ConnectionIndicator compact showRefreshButton />
|
||||
</Box>
|
||||
|
||||
<Tabs
|
||||
value={activeDocTab}
|
||||
onChange={(_, newValue) => {
|
||||
setActiveDocTab(newValue);
|
||||
setIsDocDrawerOpen(true);
|
||||
}}
|
||||
textColor="inherit"
|
||||
indicatorColor="secondary"
|
||||
>
|
||||
<Tab label="Aide Canvas" />
|
||||
<Tab label="Aide Palette" />
|
||||
<Tab label="Aide Propriétés" />
|
||||
<Tab label="Test Catalogue" />
|
||||
<Tab label="Test VWB" />
|
||||
</Tabs>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{/* Interface principale */}
|
||||
<Box sx={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
|
||||
{/* Palette d'étapes - Responsive */}
|
||||
<Box
|
||||
sx={responsiveLayout.getResponsiveStyles('palette')}
|
||||
role="complementary"
|
||||
aria-label="Palette d'étapes disponibles"
|
||||
>
|
||||
<Palette
|
||||
categories={defaultCategories}
|
||||
searchTerm={searchTerm}
|
||||
onSearch={setSearchTerm}
|
||||
onStepDrag={handleStepDrag}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Canvas principal */}
|
||||
<Box
|
||||
sx={{ flex: 1, display: 'flex', flexDirection: 'column' }}
|
||||
role="main"
|
||||
aria-label="Zone principale de création de workflow"
|
||||
>
|
||||
{/* Validateur et Exécuteur en haut */}
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
borderBottom: '1px solid #e0e0e0',
|
||||
backgroundColor: '#ffffff',
|
||||
flexDirection: responsiveLayout.isMobile ? 'column' : 'row'
|
||||
}}
|
||||
role="toolbar"
|
||||
aria-label="Outils de validation et d'exécution"
|
||||
>
|
||||
{/* Validateur */}
|
||||
<Box sx={{
|
||||
flex: 1,
|
||||
p: responsiveLayout.isMobile ? 1 : 2,
|
||||
borderRight: responsiveLayout.isMobile ? 'none' : '1px solid #e0e0e0',
|
||||
borderBottom: responsiveLayout.isMobile ? '1px solid #e0e0e0' : 'none'
|
||||
}}
|
||||
role="region"
|
||||
aria-label="Validation du workflow"
|
||||
>
|
||||
<Validator
|
||||
workflow={workflow}
|
||||
variables={variables}
|
||||
onStepHighlight={handleStepHighlight}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Exécuteur */}
|
||||
<Box
|
||||
sx={{ flex: 1, p: responsiveLayout.isMobile ? 1 : 2 }}
|
||||
role="region"
|
||||
aria-label="Exécution du workflow"
|
||||
>
|
||||
<Executor
|
||||
workflow={workflow}
|
||||
canExecute={workflow.steps.length > 0}
|
||||
onStepStateChange={handleStepStateChange}
|
||||
onExecutionComplete={handleExecutionComplete}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Canvas
|
||||
workflow={workflow}
|
||||
selectedStep={selectedStep}
|
||||
onStepSelect={handleStepSelect}
|
||||
onStepMove={handleStepMove}
|
||||
onConnection={handleConnection}
|
||||
onConnectionDelete={handleConnectionDelete}
|
||||
onStepAdd={handleStepAdd}
|
||||
onStepDelete={handleStepDelete}
|
||||
/>
|
||||
|
||||
{/* Gestionnaire de variables - Responsive */}
|
||||
<Box sx={{
|
||||
...responsiveLayout.getResponsiveStyles('variables'),
|
||||
borderTop: '1px solid #e0e0e0',
|
||||
backgroundColor: '#ffffff',
|
||||
p: responsiveLayout.isMobile ? 1 : 2,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
role="region"
|
||||
aria-label="Gestionnaire de variables"
|
||||
>
|
||||
<VariableManager
|
||||
variables={variables}
|
||||
onVariableCreate={handleVariableCreate}
|
||||
onVariableUpdate={handleVariableUpdate}
|
||||
onVariableDelete={handleVariableDelete}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Panneau de propriétés - Responsive */}
|
||||
<Box
|
||||
sx={responsiveLayout.getResponsiveStyles('properties')}
|
||||
role="complementary"
|
||||
aria-label="Panneau de propriétés de l'étape sélectionnée"
|
||||
>
|
||||
<PropertiesPanel
|
||||
selectedStep={selectedStep}
|
||||
variables={variables}
|
||||
onParameterChange={handleParameterChange}
|
||||
onVisualSelection={handleVisualSelection}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Tiroir de documentation */}
|
||||
<Drawer
|
||||
anchor="right"
|
||||
open={isDocDrawerOpen}
|
||||
onClose={() => setIsDocDrawerOpen(false)}
|
||||
sx={{
|
||||
'& .MuiDrawer-paper': {
|
||||
width: responsiveLayout.isMobile ? '100%' : 400,
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{activeDocTab === 3 ? (
|
||||
<TestCatalogLoader />
|
||||
) : activeDocTab === 4 ? (
|
||||
<VWBIntegrationTest />
|
||||
) : (
|
||||
<DocumentationTab
|
||||
toolName={docTabs[activeDocTab]?.toolName || 'canvas'}
|
||||
isActive={isDocDrawerOpen}
|
||||
onActivate={() => setIsDocDrawerOpen(true)}
|
||||
/>
|
||||
)}
|
||||
</Drawer>
|
||||
|
||||
{/* Aide contextuelle */}
|
||||
<ContextualHelp
|
||||
context={selectedStep ? 'properties' : 'canvas'}
|
||||
selectedStepType={selectedStep?.type}
|
||||
currentErrors={[]} // TODO: Intégrer avec le validateur
|
||||
isVisible={!responsiveLayout.isMobile} // Masquer sur mobile pour éviter l'encombrement
|
||||
/>
|
||||
|
||||
{/* Dialogue des raccourcis clavier */}
|
||||
<KeyboardShortcuts
|
||||
open={showKeyboardShortcuts}
|
||||
onClose={() => setShowKeyboardShortcuts(false)}
|
||||
shortcuts={keyboardNavigation.shortcuts}
|
||||
/>
|
||||
</Box>
|
||||
</ReactFlowProvider>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
</AccessibilityProvider>
|
||||
</AppErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user