@osfarm/itineraire-technique 1.1.20 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -2
- package/examples/README.md +137 -0
- package/examples/nextjs-_document.tsx +66 -0
- package/examples/nextjs-api-route.ts +122 -0
- package/examples/nextjs-app-router-editor.tsx +304 -0
- package/examples/nextjs-app-router-viewer.tsx +90 -0
- package/package.json +59 -6
- package/react/QUICKSTART.md +172 -0
- package/react/README.md +305 -0
- package/react/TikaEditor.jsx +212 -0
- package/react/TikaRenderer.jsx +116 -0
- package/react/hooks.ts +217 -0
- package/react/index.ts +19 -0
- package/react/types.ts +152 -0
- package/.github/copilot-instructions.md +0 -56
- package/.github/workflows/publish.yml +0 -34
- package/scss/styles-editor.scss +0 -149
- package/scss/styles-rendering.scss +0 -184
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TikaEditor - Composant React pour éditer un itinéraire technique
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* import TikaEditor from '@osfarm/itineraire-technique/react/TikaEditor';
|
|
6
|
+
*
|
|
7
|
+
* function MyComponent() {
|
|
8
|
+
* const [data, setData] = useState(initialData);
|
|
9
|
+
*
|
|
10
|
+
* const handleSave = (newData) => {
|
|
11
|
+
* console.log('Data saved:', newData);
|
|
12
|
+
* setData(newData);
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* return <TikaEditor initialData={data} onSave={handleSave} />;
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
20
|
+
|
|
21
|
+
const TikaEditor = ({
|
|
22
|
+
initialData = null,
|
|
23
|
+
onSave = null,
|
|
24
|
+
onExport = null,
|
|
25
|
+
className = '',
|
|
26
|
+
showWikiButtons = false,
|
|
27
|
+
enableAutoSave = false,
|
|
28
|
+
autoSaveInterval = 5000
|
|
29
|
+
}) => {
|
|
30
|
+
const containerRef = useRef(null);
|
|
31
|
+
const editorInitialized = useRef(false);
|
|
32
|
+
const autoSaveTimer = useRef(null);
|
|
33
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
34
|
+
const [currentData, setCurrentData] = useState(initialData);
|
|
35
|
+
|
|
36
|
+
// Fonction pour récupérer les données actuelles de l'éditeur
|
|
37
|
+
const getCurrentData = () => {
|
|
38
|
+
if (typeof window === 'undefined' || !window.rotation_data) return null;
|
|
39
|
+
return JSON.parse(JSON.stringify(window.rotation_data));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Fonction pour sauvegarder
|
|
43
|
+
const handleSave = () => {
|
|
44
|
+
const data = getCurrentData();
|
|
45
|
+
if (data && onSave) {
|
|
46
|
+
onSave(data);
|
|
47
|
+
setCurrentData(data);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Fonction pour exporter
|
|
52
|
+
const handleExport = () => {
|
|
53
|
+
const data = getCurrentData();
|
|
54
|
+
if (data && onExport) {
|
|
55
|
+
onExport(data);
|
|
56
|
+
} else if (data && window.exportToJsonFile) {
|
|
57
|
+
// Utiliser la fonction d'export native si pas de callback personnalisé
|
|
58
|
+
window.exportToJsonFile(data);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Auto-save
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (enableAutoSave && onSave) {
|
|
65
|
+
autoSaveTimer.current = setInterval(() => {
|
|
66
|
+
handleSave();
|
|
67
|
+
}, autoSaveInterval);
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
if (autoSaveTimer.current) {
|
|
71
|
+
clearInterval(autoSaveTimer.current);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}, [enableAutoSave, autoSaveInterval, onSave]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (typeof window === 'undefined') return;
|
|
79
|
+
|
|
80
|
+
// Vérifier les dépendances
|
|
81
|
+
const checkDependencies = () => {
|
|
82
|
+
const missing = [];
|
|
83
|
+
if (!window.$) missing.push('jQuery');
|
|
84
|
+
if (!window.echarts) missing.push('ECharts');
|
|
85
|
+
if (!window._) missing.push('Underscore');
|
|
86
|
+
if (!window.RotationRenderer) missing.push('chart-render.js');
|
|
87
|
+
|
|
88
|
+
// Vérifier les fonctions de l'éditeur
|
|
89
|
+
if (!window.refreshAttributesTable) missing.push('editor-attributes.js');
|
|
90
|
+
if (!window.refreshStepsButtonList) missing.push('editor-crops.js');
|
|
91
|
+
if (!window.exportToJsonFile) missing.push('editor-export.js');
|
|
92
|
+
if (!window.refreshInterventionsList) missing.push('editor-interventions.js');
|
|
93
|
+
|
|
94
|
+
return missing;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const missingDeps = checkDependencies();
|
|
98
|
+
if (missingDeps.length > 0) {
|
|
99
|
+
console.error('Missing dependencies for TikaEditor:', missingDeps.join(', '));
|
|
100
|
+
setIsLoading(false);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Initialiser l'éditeur seulement une fois
|
|
105
|
+
if (!editorInitialized.current && containerRef.current) {
|
|
106
|
+
editorInitialized.current = true;
|
|
107
|
+
|
|
108
|
+
// Initialiser la structure globale de données si nécessaire
|
|
109
|
+
if (!window.rotation_data) {
|
|
110
|
+
window.rotation_data = initialData || {
|
|
111
|
+
title: "Nouvel itinéraire",
|
|
112
|
+
options: {
|
|
113
|
+
view: "horizontal",
|
|
114
|
+
show_transcript: true,
|
|
115
|
+
title_top_interventions: "Cultures principales",
|
|
116
|
+
title_bottom_interventions: "Couverts et CIVE",
|
|
117
|
+
title_steps: "Rotation",
|
|
118
|
+
region: "France",
|
|
119
|
+
show_climate_diagram: false
|
|
120
|
+
},
|
|
121
|
+
steps: []
|
|
122
|
+
};
|
|
123
|
+
} else if (initialData) {
|
|
124
|
+
window.rotation_data = initialData;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Appeler les fonctions d'initialisation de l'éditeur
|
|
128
|
+
try {
|
|
129
|
+
if (window.refreshAttributesTable) window.refreshAttributesTable();
|
|
130
|
+
if (window.refreshStepsButtonList) window.refreshStepsButtonList();
|
|
131
|
+
if (window.refreshInterventionsList) window.refreshInterventionsList();
|
|
132
|
+
|
|
133
|
+
// Rendre le graphique initial
|
|
134
|
+
if (window.renderChart) {
|
|
135
|
+
window.renderChart();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setIsLoading(false);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Error initializing editor:', error);
|
|
141
|
+
setIsLoading(false);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Exposer les fonctions de save/export via window pour que le HTML interne puisse les appeler
|
|
146
|
+
window.reactEditorSave = handleSave;
|
|
147
|
+
window.reactEditorExport = handleExport;
|
|
148
|
+
|
|
149
|
+
}, [initialData]);
|
|
150
|
+
|
|
151
|
+
// Mettre à jour les données si initialData change
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (initialData && window.rotation_data) {
|
|
154
|
+
window.rotation_data = initialData;
|
|
155
|
+
if (window.refreshAttributesTable) window.refreshAttributesTable();
|
|
156
|
+
if (window.refreshStepsButtonList) window.refreshStepsButtonList();
|
|
157
|
+
if (window.refreshInterventionsList) window.refreshInterventionsList();
|
|
158
|
+
if (window.renderChart) window.renderChart();
|
|
159
|
+
}
|
|
160
|
+
}, [initialData]);
|
|
161
|
+
|
|
162
|
+
if (isLoading) {
|
|
163
|
+
return (
|
|
164
|
+
<div className={`itineraire-editor-loading ${className}`}>
|
|
165
|
+
<div className="spinner-border" role="status">
|
|
166
|
+
<span className="visually-hidden">Chargement...</span>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div ref={containerRef} className={`itineraire-editor ${className}`}>
|
|
174
|
+
{/* Boutons de contrôle React */}
|
|
175
|
+
<div className="react-editor-controls mb-3">
|
|
176
|
+
<div className="btn-group" role="group">
|
|
177
|
+
{onSave && (
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
className="btn btn-primary"
|
|
181
|
+
onClick={handleSave}
|
|
182
|
+
>
|
|
183
|
+
<i className="fa fa-save"></i> Enregistrer
|
|
184
|
+
</button>
|
|
185
|
+
)}
|
|
186
|
+
{onExport && (
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
className="btn btn-outline-primary"
|
|
190
|
+
onClick={handleExport}
|
|
191
|
+
>
|
|
192
|
+
<i className="fa fa-download"></i> Exporter JSON
|
|
193
|
+
</button>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Le contenu HTML de l'éditeur sera injecté ici via un portail ou iframe */}
|
|
199
|
+
{/* Pour l'instant, on délègue au HTML existant */}
|
|
200
|
+
<div id="editor-content-container">
|
|
201
|
+
{/* L'éditeur HTML existant sera chargé ici */}
|
|
202
|
+
<iframe
|
|
203
|
+
src="/editor.html"
|
|
204
|
+
style={{ width: '100%', height: '800px', border: 'none' }}
|
|
205
|
+
title="Éditeur d'itinéraire technique"
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export default TikaEditor;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TikaRenderer - Composant React pour visualiser un itinéraire technique
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* import TikaRenderer from '@osfarm/itineraire-technique/react/TikaRenderer';
|
|
6
|
+
*
|
|
7
|
+
* function MyComponent() {
|
|
8
|
+
* const data = { ... }; // Données JSON de l'itinéraire
|
|
9
|
+
* return <TikaRenderer data={data} />;
|
|
10
|
+
* }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React, { useEffect, useRef } from 'react';
|
|
14
|
+
|
|
15
|
+
const TikaRenderer = ({
|
|
16
|
+
data,
|
|
17
|
+
width = '100%',
|
|
18
|
+
height = 'auto',
|
|
19
|
+
className = '',
|
|
20
|
+
onItemClick = null,
|
|
21
|
+
onItemHover = null
|
|
22
|
+
}) => {
|
|
23
|
+
const containerRef = useRef(null);
|
|
24
|
+
const rendererRef = useRef(null);
|
|
25
|
+
const divId = useRef(`itk-${Math.random().toString(36).substr(2, 9)}`);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
// Vérifier que les dépendances sont chargées
|
|
29
|
+
if (typeof window === 'undefined') return;
|
|
30
|
+
|
|
31
|
+
if (!window.RotationRenderer) {
|
|
32
|
+
console.error('RotationRenderer not loaded. Make sure chart-render.js is included.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!window.echarts) {
|
|
37
|
+
console.error('ECharts not loaded. Make sure ECharts is included.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!window.$) {
|
|
42
|
+
console.error('jQuery not loaded. Make sure jQuery is included.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Créer le conteneur si nécessaire
|
|
47
|
+
if (containerRef.current && !containerRef.current.querySelector('.mainITKContainer')) {
|
|
48
|
+
// Initialiser le renderer
|
|
49
|
+
try {
|
|
50
|
+
rendererRef.current = new window.RotationRenderer(divId.current, data);
|
|
51
|
+
rendererRef.current.render();
|
|
52
|
+
|
|
53
|
+
// Attacher les événements personnalisés si fournis
|
|
54
|
+
if (onItemClick) {
|
|
55
|
+
containerRef.current.addEventListener('click', (e) => {
|
|
56
|
+
const item = e.target.closest('.rotation_item');
|
|
57
|
+
if (item) {
|
|
58
|
+
const itemId = item.dataset.id || item.id;
|
|
59
|
+
onItemClick(itemId, e);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (onItemHover) {
|
|
65
|
+
containerRef.current.addEventListener('mouseover', (e) => {
|
|
66
|
+
const item = e.target.closest('.rotation_item');
|
|
67
|
+
if (item) {
|
|
68
|
+
const itemId = item.dataset.id || item.id;
|
|
69
|
+
onItemHover(itemId, e);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Error initializing RotationRenderer:', error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Cleanup
|
|
79
|
+
return () => {
|
|
80
|
+
if (rendererRef.current && rendererRef.current.chart) {
|
|
81
|
+
try {
|
|
82
|
+
rendererRef.current.chart.dispose();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Error disposing chart:', error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}, [data, onItemClick, onItemHover]);
|
|
89
|
+
|
|
90
|
+
// Réagir aux changements de données
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (rendererRef.current && data) {
|
|
93
|
+
try {
|
|
94
|
+
// Recréer le renderer avec les nouvelles données
|
|
95
|
+
if (rendererRef.current.chart) {
|
|
96
|
+
rendererRef.current.chart.dispose();
|
|
97
|
+
}
|
|
98
|
+
rendererRef.current = new window.RotationRenderer(divId.current, data);
|
|
99
|
+
rendererRef.current.render();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Error updating chart data:', error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, [data]);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div
|
|
108
|
+
ref={containerRef}
|
|
109
|
+
id={divId.current}
|
|
110
|
+
className={`itineraire-renderer ${className}`}
|
|
111
|
+
style={{ width, height }}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default TikaRenderer;
|
package/react/hooks.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook React personnalisé pour gérer les données d'itinéraire technique
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
6
|
+
import type { ItineraireData, Step, Intervention } from './types';
|
|
7
|
+
|
|
8
|
+
export interface UseItineraireResult {
|
|
9
|
+
data: ItineraireData | null;
|
|
10
|
+
loading: boolean;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
updateData: (newData: ItineraireData) => void;
|
|
13
|
+
addStep: (step: Step) => void;
|
|
14
|
+
updateStep: (stepId: string, updates: Partial<Step>) => void;
|
|
15
|
+
deleteStep: (stepId: string) => void;
|
|
16
|
+
addIntervention: (stepId: string, intervention: Intervention) => void;
|
|
17
|
+
updateIntervention: (stepId: string, interventionId: string, updates: Partial<Intervention>) => void;
|
|
18
|
+
deleteIntervention: (stepId: string, interventionId: string) => void;
|
|
19
|
+
exportToJson: () => string;
|
|
20
|
+
importFromJson: (jsonString: string) => void;
|
|
21
|
+
reset: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Hook pour gérer les données d'itinéraire technique
|
|
26
|
+
* @param initialData Données initiales
|
|
27
|
+
* @param onUpdate Callback appelé à chaque modification
|
|
28
|
+
*/
|
|
29
|
+
export const useItineraire = (
|
|
30
|
+
initialData: ItineraireData | null = null,
|
|
31
|
+
onUpdate?: (data: ItineraireData) => void
|
|
32
|
+
): UseItineraireResult => {
|
|
33
|
+
const [data, setData] = useState<ItineraireData | null>(initialData);
|
|
34
|
+
const [loading, setLoading] = useState(false);
|
|
35
|
+
const [error, setError] = useState<Error | null>(null);
|
|
36
|
+
|
|
37
|
+
// Appeler onUpdate quand les données changent
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (data && onUpdate) {
|
|
40
|
+
onUpdate(data);
|
|
41
|
+
}
|
|
42
|
+
}, [data, onUpdate]);
|
|
43
|
+
|
|
44
|
+
const updateData = useCallback((newData: ItineraireData) => {
|
|
45
|
+
setData(newData);
|
|
46
|
+
setError(null);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const addStep = useCallback((step: Step) => {
|
|
50
|
+
setData((prevData) => {
|
|
51
|
+
if (!prevData) return prevData;
|
|
52
|
+
return {
|
|
53
|
+
...prevData,
|
|
54
|
+
steps: [...prevData.steps, step]
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const updateStep = useCallback((stepId: string, updates: Partial<Step>) => {
|
|
60
|
+
setData((prevData) => {
|
|
61
|
+
if (!prevData) return prevData;
|
|
62
|
+
return {
|
|
63
|
+
...prevData,
|
|
64
|
+
steps: prevData.steps.map((step) =>
|
|
65
|
+
step.id === stepId ? { ...step, ...updates } : step
|
|
66
|
+
)
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const deleteStep = useCallback((stepId: string) => {
|
|
72
|
+
setData((prevData) => {
|
|
73
|
+
if (!prevData) return prevData;
|
|
74
|
+
return {
|
|
75
|
+
...prevData,
|
|
76
|
+
steps: prevData.steps.filter((step) => step.id !== stepId)
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const addIntervention = useCallback((stepId: string, intervention: Intervention) => {
|
|
82
|
+
setData((prevData) => {
|
|
83
|
+
if (!prevData) return prevData;
|
|
84
|
+
return {
|
|
85
|
+
...prevData,
|
|
86
|
+
steps: prevData.steps.map((step) =>
|
|
87
|
+
step.id === stepId
|
|
88
|
+
? {
|
|
89
|
+
...step,
|
|
90
|
+
interventions: [...(step.interventions || []), intervention]
|
|
91
|
+
}
|
|
92
|
+
: step
|
|
93
|
+
)
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
const updateIntervention = useCallback(
|
|
99
|
+
(stepId: string, interventionId: string, updates: Partial<Intervention>) => {
|
|
100
|
+
setData((prevData) => {
|
|
101
|
+
if (!prevData) return prevData;
|
|
102
|
+
return {
|
|
103
|
+
...prevData,
|
|
104
|
+
steps: prevData.steps.map((step) =>
|
|
105
|
+
step.id === stepId
|
|
106
|
+
? {
|
|
107
|
+
...step,
|
|
108
|
+
interventions: (step.interventions || []).map((intervention) =>
|
|
109
|
+
intervention.id === interventionId
|
|
110
|
+
? { ...intervention, ...updates }
|
|
111
|
+
: intervention
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
: step
|
|
115
|
+
)
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
[]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const deleteIntervention = useCallback((stepId: string, interventionId: string) => {
|
|
123
|
+
setData((prevData) => {
|
|
124
|
+
if (!prevData) return prevData;
|
|
125
|
+
return {
|
|
126
|
+
...prevData,
|
|
127
|
+
steps: prevData.steps.map((step) =>
|
|
128
|
+
step.id === stepId
|
|
129
|
+
? {
|
|
130
|
+
...step,
|
|
131
|
+
interventions: (step.interventions || []).filter(
|
|
132
|
+
(intervention) => intervention.id !== interventionId
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
: step
|
|
136
|
+
)
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
const exportToJson = useCallback(() => {
|
|
142
|
+
if (!data) return '{}';
|
|
143
|
+
return JSON.stringify(data, null, 2);
|
|
144
|
+
}, [data]);
|
|
145
|
+
|
|
146
|
+
const importFromJson = useCallback((jsonString: string) => {
|
|
147
|
+
try {
|
|
148
|
+
setLoading(true);
|
|
149
|
+
const parsed = JSON.parse(jsonString);
|
|
150
|
+
setData(parsed);
|
|
151
|
+
setError(null);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
setError(err instanceof Error ? err : new Error('Invalid JSON'));
|
|
154
|
+
} finally {
|
|
155
|
+
setLoading(false);
|
|
156
|
+
}
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const reset = useCallback(() => {
|
|
160
|
+
setData(initialData);
|
|
161
|
+
setError(null);
|
|
162
|
+
}, [initialData]);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
data,
|
|
166
|
+
loading,
|
|
167
|
+
error,
|
|
168
|
+
updateData,
|
|
169
|
+
addStep,
|
|
170
|
+
updateStep,
|
|
171
|
+
deleteStep,
|
|
172
|
+
addIntervention,
|
|
173
|
+
updateIntervention,
|
|
174
|
+
deleteIntervention,
|
|
175
|
+
exportToJson,
|
|
176
|
+
importFromJson,
|
|
177
|
+
reset
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Hook pour charger les dépendances nécessaires (ECharts, jQuery, etc.)
|
|
183
|
+
*/
|
|
184
|
+
export const useItineraireDependencies = () => {
|
|
185
|
+
const [loaded, setLoaded] = useState(false);
|
|
186
|
+
const [error, setError] = useState<Error | null>(null);
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (typeof window === 'undefined') return;
|
|
190
|
+
|
|
191
|
+
const checkDependencies = () => {
|
|
192
|
+
const hasEcharts = !!window.echarts;
|
|
193
|
+
const hasJQuery = !!window.$;
|
|
194
|
+
const hasUnderscore = !!window._;
|
|
195
|
+
const hasRotationRenderer = !!window.RotationRenderer;
|
|
196
|
+
|
|
197
|
+
return hasEcharts && hasJQuery && hasUnderscore && hasRotationRenderer;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (checkDependencies()) {
|
|
201
|
+
setLoaded(true);
|
|
202
|
+
} else {
|
|
203
|
+
// Attendre un peu que les scripts se chargent
|
|
204
|
+
const timeout = setTimeout(() => {
|
|
205
|
+
if (checkDependencies()) {
|
|
206
|
+
setLoaded(true);
|
|
207
|
+
} else {
|
|
208
|
+
setError(new Error('Required dependencies not loaded'));
|
|
209
|
+
}
|
|
210
|
+
}, 1000);
|
|
211
|
+
|
|
212
|
+
return () => clearTimeout(timeout);
|
|
213
|
+
}
|
|
214
|
+
}, []);
|
|
215
|
+
|
|
216
|
+
return { loaded, error };
|
|
217
|
+
};
|
package/react/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Point d'entrée principal pour les composants React
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { default as TikaRenderer } from './TikaRenderer';
|
|
6
|
+
export { default as TikaEditor } from './TikaEditor';
|
|
7
|
+
export { useItineraire, useItineraireDependencies } from './hooks';
|
|
8
|
+
export type {
|
|
9
|
+
ItineraireData,
|
|
10
|
+
ItineraireOptions,
|
|
11
|
+
Step,
|
|
12
|
+
Intervention,
|
|
13
|
+
ClimateData,
|
|
14
|
+
RotationData,
|
|
15
|
+
TimelineData,
|
|
16
|
+
TikaRendererProps,
|
|
17
|
+
TikaEditorProps,
|
|
18
|
+
IRotationRenderer
|
|
19
|
+
} from './types';
|