@osfarm/itineraire-technique 1.2.0 → 1.2.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/.github/copilot-instructions.md +56 -0
- package/.github/workflows/publish.yml +34 -0
- package/README.md +2 -34
- package/css/styles-editor.css +1 -1
- package/css/styles-editor.css.map +1 -1
- package/editor.html +38 -748
- package/js/chart-render.js +16 -11
- package/js/editor-interventions.js +315 -189
- package/js/editor-loader-default.js +238 -0
- package/js/editor-loader-itinera.js +135 -0
- package/js/{editor-wiki-editor.js → editor-loader-wiki.js} +99 -11
- package/js/editor-main.js +752 -0
- package/js/intervention.js +12 -0
- package/js/step-model.js +69 -0
- package/package.json +6 -59
- package/scss/styles-editor.scss +145 -0
- package/scss/styles-rendering.scss +184 -0
- package/examples/README.md +0 -137
- package/examples/nextjs-_document.tsx +0 -66
- package/examples/nextjs-api-route.ts +0 -122
- package/examples/nextjs-app-router-editor.tsx +0 -304
- package/examples/nextjs-app-router-viewer.tsx +0 -90
- package/js/editor-attributes.js +0 -99
- package/js/editor-crops.js +0 -136
- package/js/editor-export.js +0 -118
- package/react/QUICKSTART.md +0 -172
- package/react/README.md +0 -305
- package/react/TikaEditor.jsx +0 -212
- package/react/TikaRenderer.jsx +0 -116
- package/react/hooks.ts +0 -217
- package/react/index.ts +0 -19
- package/react/types.ts +0 -152
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exemple d'API route pour sauvegarder/charger des itinéraires
|
|
3
|
-
* Fichier: app/api/itineraire/route.ts (Next.js App Router)
|
|
4
|
-
* ou pages/api/itineraire.ts (Next.js Pages Router)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
-
import { promises as fs } from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
|
|
11
|
-
// Dossier de stockage des itinéraires
|
|
12
|
-
const ITINERAIRES_DIR = path.join(process.cwd(), 'data', 'itineraires');
|
|
13
|
-
|
|
14
|
-
// Créer le dossier s'il n'existe pas
|
|
15
|
-
async function ensureDir() {
|
|
16
|
-
try {
|
|
17
|
-
await fs.access(ITINERAIRES_DIR);
|
|
18
|
-
} catch {
|
|
19
|
-
await fs.mkdir(ITINERAIRES_DIR, { recursive: true });
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// GET - Récupérer un itinéraire
|
|
24
|
-
export async function GET(request: NextRequest) {
|
|
25
|
-
const { searchParams } = new URL(request.url);
|
|
26
|
-
const id = searchParams.get('id');
|
|
27
|
-
|
|
28
|
-
if (!id) {
|
|
29
|
-
return NextResponse.json({ error: 'ID manquant' }, { status: 400 });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
await ensureDir();
|
|
34
|
-
const filePath = path.join(ITINERAIRES_DIR, `${id}.json`);
|
|
35
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
36
|
-
return NextResponse.json(JSON.parse(data));
|
|
37
|
-
} catch (error) {
|
|
38
|
-
return NextResponse.json(
|
|
39
|
-
{ error: 'Itinéraire non trouvé' },
|
|
40
|
-
{ status: 404 }
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// POST - Sauvegarder un itinéraire
|
|
46
|
-
export async function POST(request: NextRequest) {
|
|
47
|
-
try {
|
|
48
|
-
const data = await request.json();
|
|
49
|
-
|
|
50
|
-
// Validation basique
|
|
51
|
-
if (!data.title || !data.options || !data.steps) {
|
|
52
|
-
return NextResponse.json(
|
|
53
|
-
{ error: 'Données invalides' },
|
|
54
|
-
{ status: 400 }
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
await ensureDir();
|
|
59
|
-
|
|
60
|
-
// Générer un ID si nécessaire
|
|
61
|
-
const id = data.id || `itk-${Date.now()}`;
|
|
62
|
-
const filePath = path.join(ITINERAIRES_DIR, `${id}.json`);
|
|
63
|
-
|
|
64
|
-
// Sauvegarder
|
|
65
|
-
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
66
|
-
|
|
67
|
-
return NextResponse.json({ success: true, id });
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error('Erreur de sauvegarde:', error);
|
|
70
|
-
return NextResponse.json(
|
|
71
|
-
{ error: 'Erreur de sauvegarde' },
|
|
72
|
-
{ status: 500 }
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// DELETE - Supprimer un itinéraire
|
|
78
|
-
export async function DELETE(request: NextRequest) {
|
|
79
|
-
const { searchParams } = new URL(request.url);
|
|
80
|
-
const id = searchParams.get('id');
|
|
81
|
-
|
|
82
|
-
if (!id) {
|
|
83
|
-
return NextResponse.json({ error: 'ID manquant' }, { status: 400 });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const filePath = path.join(ITINERAIRES_DIR, `${id}.json`);
|
|
88
|
-
await fs.unlink(filePath);
|
|
89
|
-
return NextResponse.json({ success: true });
|
|
90
|
-
} catch (error) {
|
|
91
|
-
return NextResponse.json(
|
|
92
|
-
{ error: 'Erreur de suppression' },
|
|
93
|
-
{ status: 500 }
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Liste de tous les itinéraires (pour Pages Router)
|
|
99
|
-
// Fichier: pages/api/itineraires/list.ts
|
|
100
|
-
export async function listItineraires() {
|
|
101
|
-
await ensureDir();
|
|
102
|
-
const files = await fs.readdir(ITINERAIRES_DIR);
|
|
103
|
-
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
104
|
-
|
|
105
|
-
const itineraires = await Promise.all(
|
|
106
|
-
jsonFiles.map(async (file) => {
|
|
107
|
-
const content = await fs.readFile(
|
|
108
|
-
path.join(ITINERAIRES_DIR, file),
|
|
109
|
-
'utf-8'
|
|
110
|
-
);
|
|
111
|
-
const data = JSON.parse(content);
|
|
112
|
-
return {
|
|
113
|
-
id: file.replace('.json', ''),
|
|
114
|
-
title: data.title,
|
|
115
|
-
stepsCount: data.steps?.length || 0,
|
|
116
|
-
updatedAt: (await fs.stat(path.join(ITINERAIRES_DIR, file))).mtime
|
|
117
|
-
};
|
|
118
|
-
})
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
return itineraires;
|
|
122
|
-
}
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exemple d'utilisation de l'éditeur avec Next.js App Router
|
|
3
|
-
* Fichier: app/editor/page.tsx
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import { useItineraire, useItineraireDependencies } from '@osfarm/itineraire-technique/react';
|
|
9
|
-
import { TikaRenderer } from '@osfarm/itineraire-technique/react';
|
|
10
|
-
import { useState } from 'react';
|
|
11
|
-
import type { ItineraireData, Step, Intervention } from '@osfarm/itineraire-technique/react';
|
|
12
|
-
|
|
13
|
-
export default function EditorPage() {
|
|
14
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
15
|
-
const [saveMessage, setSaveMessage] = useState<string | null>(null);
|
|
16
|
-
|
|
17
|
-
// Vérifier que les dépendances sont chargées
|
|
18
|
-
const { loaded: depsLoaded, error: depsError } = useItineraireDependencies();
|
|
19
|
-
|
|
20
|
-
// Données initiales
|
|
21
|
-
const initialData: ItineraireData = {
|
|
22
|
-
title: "Nouvelle rotation",
|
|
23
|
-
options: {
|
|
24
|
-
view: "horizontal",
|
|
25
|
-
show_transcript: true,
|
|
26
|
-
title_top_interventions: "Cultures principales",
|
|
27
|
-
title_bottom_interventions: "Couverts et CIVE",
|
|
28
|
-
title_steps: "Rotation",
|
|
29
|
-
region: "France",
|
|
30
|
-
show_climate_diagram: false
|
|
31
|
-
},
|
|
32
|
-
steps: []
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const {
|
|
36
|
-
data,
|
|
37
|
-
loading,
|
|
38
|
-
error,
|
|
39
|
-
addStep,
|
|
40
|
-
updateStep,
|
|
41
|
-
deleteStep,
|
|
42
|
-
addIntervention,
|
|
43
|
-
deleteIntervention,
|
|
44
|
-
exportToJson,
|
|
45
|
-
importFromJson
|
|
46
|
-
} = useItineraire(initialData);
|
|
47
|
-
|
|
48
|
-
// Sauvegarder vers une API
|
|
49
|
-
const handleSave = async () => {
|
|
50
|
-
if (!data) return;
|
|
51
|
-
|
|
52
|
-
setIsSaving(true);
|
|
53
|
-
setSaveMessage(null);
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const response = await fetch('/api/itineraire', {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
headers: {
|
|
59
|
-
'Content-Type': 'application/json',
|
|
60
|
-
},
|
|
61
|
-
body: JSON.stringify(data),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (!response.ok) throw new Error('Erreur de sauvegarde');
|
|
65
|
-
|
|
66
|
-
setSaveMessage('Sauvegarde réussie !');
|
|
67
|
-
setTimeout(() => setSaveMessage(null), 3000);
|
|
68
|
-
} catch (err) {
|
|
69
|
-
setSaveMessage(`Erreur: ${err instanceof Error ? err.message : 'Erreur inconnue'}`);
|
|
70
|
-
} finally {
|
|
71
|
-
setIsSaving(false);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// Exporter en JSON
|
|
76
|
-
const handleExport = () => {
|
|
77
|
-
const json = exportToJson();
|
|
78
|
-
const blob = new Blob([json], { type: 'application/json' });
|
|
79
|
-
const url = URL.createObjectURL(blob);
|
|
80
|
-
const a = document.createElement('a');
|
|
81
|
-
a.href = url;
|
|
82
|
-
a.download = `itineraire-${Date.now()}.json`;
|
|
83
|
-
document.body.appendChild(a);
|
|
84
|
-
a.click();
|
|
85
|
-
document.body.removeChild(a);
|
|
86
|
-
URL.revokeObjectURL(url);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Importer depuis JSON
|
|
90
|
-
const handleImport = () => {
|
|
91
|
-
const input = document.createElement('input');
|
|
92
|
-
input.type = 'file';
|
|
93
|
-
input.accept = 'application/json';
|
|
94
|
-
input.onchange = (e) => {
|
|
95
|
-
const file = (e.target as HTMLInputElement).files?.[0];
|
|
96
|
-
if (file) {
|
|
97
|
-
const reader = new FileReader();
|
|
98
|
-
reader.onload = (e) => {
|
|
99
|
-
const content = e.target?.result as string;
|
|
100
|
-
try {
|
|
101
|
-
importFromJson(content);
|
|
102
|
-
setSaveMessage('Import réussi !');
|
|
103
|
-
setTimeout(() => setSaveMessage(null), 3000);
|
|
104
|
-
} catch (err) {
|
|
105
|
-
setSaveMessage(`Erreur d'import: ${err instanceof Error ? err.message : 'Fichier invalide'}`);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
reader.readAsText(file);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
input.click();
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// Ajouter une nouvelle étape
|
|
115
|
-
const handleAddStep = () => {
|
|
116
|
-
const now = new Date();
|
|
117
|
-
const endDate = new Date(now);
|
|
118
|
-
endDate.setMonth(endDate.getMonth() + 3);
|
|
119
|
-
|
|
120
|
-
const newStep: Step = {
|
|
121
|
-
id: crypto.randomUUID(),
|
|
122
|
-
name: `Nouvelle culture ${data?.steps.length ? data.steps.length + 1 : 1}`,
|
|
123
|
-
startDate: now.toISOString(),
|
|
124
|
-
endDate: endDate.toISOString(),
|
|
125
|
-
color: `#${Math.floor(Math.random()*16777215).toString(16)}`,
|
|
126
|
-
description: '',
|
|
127
|
-
interventions: []
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
addStep(newStep);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// Ajouter une intervention à une étape
|
|
134
|
-
const handleAddIntervention = (stepId: string) => {
|
|
135
|
-
const newIntervention: Intervention = {
|
|
136
|
-
id: crypto.randomUUID(),
|
|
137
|
-
day: '0',
|
|
138
|
-
name: 'Nouvelle intervention',
|
|
139
|
-
type: 'intervention_top',
|
|
140
|
-
description: ''
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
addIntervention(stepId, newIntervention);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
if (!depsLoaded) {
|
|
147
|
-
return (
|
|
148
|
-
<div className="container py-5 text-center">
|
|
149
|
-
<div className="spinner-border" role="status">
|
|
150
|
-
<span className="visually-hidden">Chargement des dépendances...</span>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (depsError) {
|
|
157
|
-
return (
|
|
158
|
-
<div className="container py-5">
|
|
159
|
-
<div className="alert alert-danger">
|
|
160
|
-
Erreur de chargement: {depsError.message}
|
|
161
|
-
<br />
|
|
162
|
-
<small>Assurez-vous que les scripts sont bien chargés dans _document.tsx</small>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (loading) {
|
|
169
|
-
return (
|
|
170
|
-
<div className="container py-5 text-center">
|
|
171
|
-
<div className="spinner-border" role="status">
|
|
172
|
-
<span className="visually-hidden">Chargement...</span>
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (error) {
|
|
179
|
-
return (
|
|
180
|
-
<div className="container py-5">
|
|
181
|
-
<div className="alert alert-danger">
|
|
182
|
-
Erreur: {error.message}
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<div className="container-fluid py-4">
|
|
190
|
-
{/* En-tête avec boutons d'action */}
|
|
191
|
-
<div className="row mb-4">
|
|
192
|
-
<div className="col-12">
|
|
193
|
-
<div className="d-flex justify-content-between align-items-center">
|
|
194
|
-
<h1>Éditeur d'Itinéraire Technique</h1>
|
|
195
|
-
|
|
196
|
-
<div className="btn-group" role="group">
|
|
197
|
-
<button
|
|
198
|
-
className="btn btn-primary"
|
|
199
|
-
onClick={handleSave}
|
|
200
|
-
disabled={isSaving}
|
|
201
|
-
>
|
|
202
|
-
<i className="fa fa-save"></i>
|
|
203
|
-
{isSaving ? ' Sauvegarde...' : ' Sauvegarder'}
|
|
204
|
-
</button>
|
|
205
|
-
<button
|
|
206
|
-
className="btn btn-outline-secondary"
|
|
207
|
-
onClick={handleExport}
|
|
208
|
-
>
|
|
209
|
-
<i className="fa fa-download"></i> Exporter JSON
|
|
210
|
-
</button>
|
|
211
|
-
<button
|
|
212
|
-
className="btn btn-outline-secondary"
|
|
213
|
-
onClick={handleImport}
|
|
214
|
-
>
|
|
215
|
-
<i className="fa fa-upload"></i> Importer JSON
|
|
216
|
-
</button>
|
|
217
|
-
</div>
|
|
218
|
-
</div>
|
|
219
|
-
|
|
220
|
-
{saveMessage && (
|
|
221
|
-
<div className={`alert mt-3 ${saveMessage.includes('Erreur') ? 'alert-danger' : 'alert-success'}`}>
|
|
222
|
-
{saveMessage}
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
|
|
228
|
-
{/* Zone d'édition */}
|
|
229
|
-
<div className="row">
|
|
230
|
-
<div className="col-lg-3">
|
|
231
|
-
<div className="card">
|
|
232
|
-
<div className="card-header d-flex justify-content-between align-items-center">
|
|
233
|
-
<h5 className="mb-0">Étapes</h5>
|
|
234
|
-
<button
|
|
235
|
-
className="btn btn-sm btn-success"
|
|
236
|
-
onClick={handleAddStep}
|
|
237
|
-
>
|
|
238
|
-
<i className="fa fa-plus"></i>
|
|
239
|
-
</button>
|
|
240
|
-
</div>
|
|
241
|
-
<div className="card-body">
|
|
242
|
-
{data?.steps.length === 0 ? (
|
|
243
|
-
<p className="text-muted text-center">Aucune étape</p>
|
|
244
|
-
) : (
|
|
245
|
-
<div className="list-group">
|
|
246
|
-
{data?.steps.map((step) => (
|
|
247
|
-
<div key={step.id} className="list-group-item">
|
|
248
|
-
<div className="d-flex justify-content-between align-items-center mb-2">
|
|
249
|
-
<strong>{step.name}</strong>
|
|
250
|
-
<div className="btn-group btn-group-sm">
|
|
251
|
-
<button
|
|
252
|
-
className="btn btn-outline-primary"
|
|
253
|
-
onClick={() => handleAddIntervention(step.id)}
|
|
254
|
-
title="Ajouter intervention"
|
|
255
|
-
>
|
|
256
|
-
<i className="fa fa-plus"></i>
|
|
257
|
-
</button>
|
|
258
|
-
<button
|
|
259
|
-
className="btn btn-outline-danger"
|
|
260
|
-
onClick={() => deleteStep(step.id)}
|
|
261
|
-
title="Supprimer"
|
|
262
|
-
>
|
|
263
|
-
<i className="fa fa-trash"></i>
|
|
264
|
-
</button>
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
<small className="text-muted">
|
|
268
|
-
{new Date(step.startDate).toLocaleDateString()} -
|
|
269
|
-
{new Date(step.endDate).toLocaleDateString()}
|
|
270
|
-
</small>
|
|
271
|
-
{step.interventions && step.interventions.length > 0 && (
|
|
272
|
-
<div className="mt-2">
|
|
273
|
-
<small>{step.interventions.length} intervention(s)</small>
|
|
274
|
-
</div>
|
|
275
|
-
)}
|
|
276
|
-
</div>
|
|
277
|
-
))}
|
|
278
|
-
</div>
|
|
279
|
-
)}
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
283
|
-
|
|
284
|
-
<div className="col-lg-9">
|
|
285
|
-
<div className="card">
|
|
286
|
-
<div className="card-header">
|
|
287
|
-
<h5 className="mb-0">Aperçu</h5>
|
|
288
|
-
</div>
|
|
289
|
-
<div className="card-body">
|
|
290
|
-
{data && data.steps.length > 0 ? (
|
|
291
|
-
<TikaRenderer data={data} width="100%" height="auto" />
|
|
292
|
-
) : (
|
|
293
|
-
<div className="text-center text-muted py-5">
|
|
294
|
-
<i className="fa fa-info-circle fa-3x mb-3"></i>
|
|
295
|
-
<p>Ajoutez des étapes pour voir l'aperçu</p>
|
|
296
|
-
</div>
|
|
297
|
-
)}
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
303
|
-
);
|
|
304
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exemple d'utilisation avec Next.js App Router
|
|
3
|
-
* Fichier: app/itineraire/page.tsx
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import { TikaRenderer } from '@osfarm/itineraire-technique/react';
|
|
9
|
-
import { useEffect, useState } from 'react';
|
|
10
|
-
import type { ItineraireData } from '@osfarm/itineraire-technique/react';
|
|
11
|
-
|
|
12
|
-
export default function ItinerairePage() {
|
|
13
|
-
const [data, setData] = useState<ItineraireData | null>(null);
|
|
14
|
-
const [loading, setLoading] = useState(true);
|
|
15
|
-
const [error, setError] = useState<string | null>(null);
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
// Charger les données depuis votre API ou fichier JSON
|
|
19
|
-
fetch('/test/test.json')
|
|
20
|
-
.then(response => {
|
|
21
|
-
if (!response.ok) throw new Error('Erreur de chargement');
|
|
22
|
-
return response.json();
|
|
23
|
-
})
|
|
24
|
-
.then(data => {
|
|
25
|
-
setData(data);
|
|
26
|
-
setLoading(false);
|
|
27
|
-
})
|
|
28
|
-
.catch(err => {
|
|
29
|
-
setError(err.message);
|
|
30
|
-
setLoading(false);
|
|
31
|
-
});
|
|
32
|
-
}, []);
|
|
33
|
-
|
|
34
|
-
const handleItemClick = (itemId: string, event: Event) => {
|
|
35
|
-
console.log('Élément cliqué:', itemId);
|
|
36
|
-
// Vous pouvez ajouter votre logique ici (modal, navigation, etc.)
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const handleItemHover = (itemId: string, event: Event) => {
|
|
40
|
-
console.log('Survol:', itemId);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
if (loading) {
|
|
44
|
-
return (
|
|
45
|
-
<div className="container py-5 text-center">
|
|
46
|
-
<div className="spinner-border" role="status">
|
|
47
|
-
<span className="visually-hidden">Chargement...</span>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (error) {
|
|
54
|
-
return (
|
|
55
|
-
<div className="container py-5">
|
|
56
|
-
<div className="alert alert-danger" role="alert">
|
|
57
|
-
Erreur: {error}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!data) {
|
|
64
|
-
return (
|
|
65
|
-
<div className="container py-5">
|
|
66
|
-
<div className="alert alert-info" role="alert">
|
|
67
|
-
Aucune donnée à afficher
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className="container-fluid py-5">
|
|
75
|
-
<div className="row">
|
|
76
|
-
<div className="col-12">
|
|
77
|
-
<h1 className="mb-4">{data.title}</h1>
|
|
78
|
-
<TikaRenderer
|
|
79
|
-
data={data}
|
|
80
|
-
width="100%"
|
|
81
|
-
height="auto"
|
|
82
|
-
onItemClick={handleItemClick}
|
|
83
|
-
onItemHover={handleItemHover}
|
|
84
|
-
className="shadow-sm"
|
|
85
|
-
/>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
package/js/editor-attributes.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
function addOrUpdateAttributeClickEvent() {
|
|
2
|
-
let id = getInputValue("attributeId");
|
|
3
|
-
let key = getInputValue("attributeName");
|
|
4
|
-
let value = getInputValue("attributeValue");
|
|
5
|
-
|
|
6
|
-
if (id != "") {
|
|
7
|
-
selectedStep.updateAttribute(id, key, value);
|
|
8
|
-
} else {
|
|
9
|
-
selectedStep.addAttribute(key, value);
|
|
10
|
-
|
|
11
|
-
document.getElementById("newAttributeButton").classList.remove("d-none");
|
|
12
|
-
getAndCleanElement("newAttributeContainer");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
refreshAllTables();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function refreshAttributesTable() {
|
|
19
|
-
let attributesContainer = document.getElementById("attributesContainer");
|
|
20
|
-
attributesContainer.innerHTML = "";
|
|
21
|
-
|
|
22
|
-
if (selectedStep && selectedStep.getStep().attributes) {
|
|
23
|
-
selectedStep.getStep().attributes.forEach((attribute) => {
|
|
24
|
-
const rowDiv = createAttributeRow(attribute);
|
|
25
|
-
attributesContainer.appendChild(rowDiv);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function createAttributeRow(attribute) {
|
|
31
|
-
let rowDiv = document.createElement("div");
|
|
32
|
-
rowDiv.className = "row mb-2 attribute-row editable-row position-relative";
|
|
33
|
-
|
|
34
|
-
let nameValueDiv = createAttributeNameAndValueColumn(attribute);
|
|
35
|
-
rowDiv.appendChild(nameValueDiv);
|
|
36
|
-
|
|
37
|
-
addEditAndRemoveButtons(
|
|
38
|
-
rowDiv,
|
|
39
|
-
attribute.id,
|
|
40
|
-
function() {
|
|
41
|
-
createAttributForm(attribute.id, attribute.name, attribute.value, rowDiv);
|
|
42
|
-
},
|
|
43
|
-
function(id) {
|
|
44
|
-
selectedStep.removeAttribute(id);
|
|
45
|
-
refreshAllTables();
|
|
46
|
-
},
|
|
47
|
-
null,
|
|
48
|
-
'btn-group-vertical'
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
return rowDiv;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function createAttributeNameAndValueColumn(attribute) {
|
|
55
|
-
let nameValueDiv = document.createElement("div");
|
|
56
|
-
nameValueDiv.className = "col";
|
|
57
|
-
nameValueDiv.innerHTML = `<strong>${attribute.name}</strong></br> ${attribute.value}`;
|
|
58
|
-
|
|
59
|
-
return nameValueDiv;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function createAttributForm(id, name, value, row) {
|
|
63
|
-
id = id || "";
|
|
64
|
-
name = name || "";
|
|
65
|
-
value = value || "";
|
|
66
|
-
|
|
67
|
-
const formContainer = document.createElement("div");
|
|
68
|
-
formContainer.innerHTML = `
|
|
69
|
-
<form>
|
|
70
|
-
<div class="row card-white edit-attribute-view mb-2">
|
|
71
|
-
<input type="hidden" id="attributeId" value="${id}">
|
|
72
|
-
<div class="col-12 mb-2">
|
|
73
|
-
<label for="attributeName" class="form-label">Nom</label>
|
|
74
|
-
<input type="text" id="attributeName" class="form-control" placeholder="Nom" value="${name}">
|
|
75
|
-
</div>
|
|
76
|
-
<div class="col-12 mb-2">
|
|
77
|
-
<input type="text" id="attributeValue" class="form-control" placeholder="Description" value="${value}">
|
|
78
|
-
</div>
|
|
79
|
-
<div class="col-12 mb-2">
|
|
80
|
-
<button type="button" onclick="addOrUpdateAttributeClickEvent()"
|
|
81
|
-
class="w-100 btn btn-outline-primary primary-button">Valider</button>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</form>
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
if (row) { //we are editing an attribute
|
|
88
|
-
row.replaceWith(formContainer);
|
|
89
|
-
} else { //we are adding an attribute
|
|
90
|
-
document.getElementById("newAttributeContainer").appendChild(formContainer);
|
|
91
|
-
document.getElementById("newAttributeButton").classList.add("d-none");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (name === "") {
|
|
95
|
-
document.getElementById("attributeName").focus();
|
|
96
|
-
} else {
|
|
97
|
-
document.getElementById("attributeValue").focus();
|
|
98
|
-
}
|
|
99
|
-
}
|