@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
package/js/editor-crops.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
function addNewStepClickEvent() {
|
|
2
|
-
createAndSelectEmptyCrop();
|
|
3
|
-
loadSelectedStepToEditor(selectedStep);
|
|
4
|
-
displayCropDetailView();
|
|
5
|
-
refreshAllTables();
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function createAndSelectEmptyCrop() {
|
|
9
|
-
let crop = new StepModel({
|
|
10
|
-
startDate: getRotationEndDate(),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
crop.setDurationInMonths(2)
|
|
14
|
-
crops.steps.push(crop.getStep());
|
|
15
|
-
|
|
16
|
-
//select last created crop to be editable
|
|
17
|
-
selectedStep = crop;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function loadSelectedStepToEditor(aStep) {
|
|
21
|
-
setInputValue("cropName", aStep.getStep().name);
|
|
22
|
-
setInputValue("cropColor", aStep.getStep().color);
|
|
23
|
-
setInputValue("cropStartDate", aStep.getStep().startDate.toISOString().split('T')[0]);
|
|
24
|
-
setInputValue("cropEndDate", aStep.getStep().endDate.toISOString().split('T')[0]);
|
|
25
|
-
setInputValue("cropDescription", aStep.getStep().description);
|
|
26
|
-
document.getElementById("cropSecondary").checked = aStep.getStep().secondary_crop || false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function refreshStepsButtonList() {
|
|
30
|
-
let cropsContainer = $("#cropsContainer");
|
|
31
|
-
cropsContainer.html('');
|
|
32
|
-
|
|
33
|
-
crops.steps.forEach((crop) => {
|
|
34
|
-
const rowDiv = createCropRow(crop);
|
|
35
|
-
cropsContainer.append(rowDiv);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
cropsContainer.sortable("refresh");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createCropRow(crop) {
|
|
42
|
-
let step = new StepModel(crop); // in case crop is a plain object, convert to Crop instance
|
|
43
|
-
|
|
44
|
-
let rowDiv = $('<div class="row mb-2 step-row editable-row position-relative" data-id="'+step.getStep().id +'"></div>');
|
|
45
|
-
|
|
46
|
-
rowDiv.append($('<div class="col"></div>')
|
|
47
|
-
.append($('<i class="fa fa-bars drag-handle" aria-hidden="true"></i>'))
|
|
48
|
-
.append($('<strong>' + step.getStep().name + '</strong>')));
|
|
49
|
-
|
|
50
|
-
addEditAndRemoveButtons(rowDiv,
|
|
51
|
-
step.getStep().id,
|
|
52
|
-
function () {
|
|
53
|
-
console.log("Selected step:", step.getStep().name);
|
|
54
|
-
SelectStep(step);
|
|
55
|
-
},
|
|
56
|
-
function(id) {
|
|
57
|
-
crops.steps = crops.steps.filter(function (crop) { return crop.id != id })
|
|
58
|
-
|
|
59
|
-
refreshAllTables();
|
|
60
|
-
displayCropListView();
|
|
61
|
-
},
|
|
62
|
-
function(id) {
|
|
63
|
-
duplicateStep(id);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
rowDiv.click();
|
|
67
|
-
|
|
68
|
-
return rowDiv;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function duplicateStep(stepId) {
|
|
72
|
-
// Find the step to duplicate
|
|
73
|
-
let originalStep = crops.steps.find(crop => crop.id === stepId);
|
|
74
|
-
if (!originalStep) return;
|
|
75
|
-
|
|
76
|
-
// Get the latest end date in the rotation
|
|
77
|
-
let latestEndDate = getRotationEndDate();
|
|
78
|
-
|
|
79
|
-
// Calculate the duration of the original step
|
|
80
|
-
let originalStart = new Date(originalStep.startDate);
|
|
81
|
-
let originalEnd = new Date(originalStep.endDate);
|
|
82
|
-
|
|
83
|
-
// Calculate how many years to add to position after the latest step
|
|
84
|
-
let yearsToAdd = 0;
|
|
85
|
-
let newStartDate = new Date(originalStart);
|
|
86
|
-
let newEndDate = new Date(originalEnd);
|
|
87
|
-
|
|
88
|
-
// Keep adding years until the new start date is after the latest end date
|
|
89
|
-
while (newStartDate < latestEndDate) {
|
|
90
|
-
yearsToAdd++;
|
|
91
|
-
newStartDate = new Date(originalStart);
|
|
92
|
-
newStartDate.setFullYear(originalStart.getFullYear() + yearsToAdd);
|
|
93
|
-
newEndDate.setFullYear(originalEnd.getFullYear() + yearsToAdd);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Create the new step with all properties cloned
|
|
97
|
-
let newStep = {
|
|
98
|
-
name: originalStep.name,
|
|
99
|
-
color: originalStep.color,
|
|
100
|
-
startDate: newStartDate,
|
|
101
|
-
endDate: newEndDate,
|
|
102
|
-
description: originalStep.description,
|
|
103
|
-
secondary_crop: originalStep.secondary_crop || false,
|
|
104
|
-
useDefaultColor: originalStep.useDefaultColor,
|
|
105
|
-
useDefaultStartDate: originalStep.useDefaultStartDate,
|
|
106
|
-
useDefaultEndDate: originalStep.useDefaultEndDate,
|
|
107
|
-
interventions: originalStep.interventions ? originalStep.interventions.map(i => ({
|
|
108
|
-
day: i.day,
|
|
109
|
-
name: i.name,
|
|
110
|
-
type: i.type,
|
|
111
|
-
description: i.description
|
|
112
|
-
})) : [],
|
|
113
|
-
attributes: originalStep.attributes ? originalStep.attributes.map(a => ({
|
|
114
|
-
name: a.name,
|
|
115
|
-
value: a.value
|
|
116
|
-
})) : []
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Create a StepModel instance to ensure proper initialization
|
|
120
|
-
let stepModel = new StepModel(newStep);
|
|
121
|
-
|
|
122
|
-
// Add the duplicated step to the rotation
|
|
123
|
-
crops.steps.push(stepModel.getStep());
|
|
124
|
-
|
|
125
|
-
// Refresh the UI
|
|
126
|
-
refreshAllTables();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function SelectStep(crop) {
|
|
130
|
-
selectedStep = crop;
|
|
131
|
-
loadSelectedStepToEditor(selectedStep);
|
|
132
|
-
displayCropDetailView();
|
|
133
|
-
|
|
134
|
-
refreshAttributesTable();
|
|
135
|
-
refreshInterventionsTable();
|
|
136
|
-
}
|
package/js/editor-export.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
function exportToJsonFile(data, fileName = 'export-itk.json') {
|
|
2
|
-
const jsonData = JSON.stringify(data, null, 2);
|
|
3
|
-
const blob = new Blob([jsonData], { type: 'application/json' });
|
|
4
|
-
const url = URL.createObjectURL(blob);
|
|
5
|
-
|
|
6
|
-
const a = document.createElement('a');
|
|
7
|
-
a.href = url;
|
|
8
|
-
a.download = fileName;
|
|
9
|
-
a.click();
|
|
10
|
-
|
|
11
|
-
URL.revokeObjectURL(url);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function importFromJsonFile() {
|
|
15
|
-
showConfirmationModal(() => {
|
|
16
|
-
openFileInput();
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function importFromTestJson() {
|
|
21
|
-
showConfirmationModal(() => {
|
|
22
|
-
importTestJSON();
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function importTestJSON() {
|
|
27
|
-
fetch('test/test.json')
|
|
28
|
-
.then(response => {
|
|
29
|
-
if (!response.ok) {
|
|
30
|
-
throw new Error("Erreur HTTP " + response.status);
|
|
31
|
-
}
|
|
32
|
-
return response.json();
|
|
33
|
-
})
|
|
34
|
-
.then(data => {
|
|
35
|
-
console.log("Données JSON :", data);
|
|
36
|
-
data.steps.forEach(step => {
|
|
37
|
-
let sm = new StepModel(step)
|
|
38
|
-
sm.setAsEdited();
|
|
39
|
-
});
|
|
40
|
-
reloadCropsFromJson(data);
|
|
41
|
-
})
|
|
42
|
-
.catch(error => {
|
|
43
|
-
console.error("Impossible de charger le JSON :", error);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function wipe() {
|
|
48
|
-
showConfirmationModal(() => {
|
|
49
|
-
let crops = {
|
|
50
|
-
"title": DEFAULT_TITLE,
|
|
51
|
-
"options": {
|
|
52
|
-
"view": "horizontal",
|
|
53
|
-
"show_transcript": true,
|
|
54
|
-
"title_top_interventions": "Contrôle adventices",
|
|
55
|
-
"title_bottom_interventions": "Autres interventions",
|
|
56
|
-
"title_steps": "Étapes de la rotation dans la parcelle",
|
|
57
|
-
},
|
|
58
|
-
"steps": []
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
reloadCropsFromJson(crops);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function openFileInput() {
|
|
66
|
-
const input = document.createElement('input');
|
|
67
|
-
input.type = 'file';
|
|
68
|
-
input.accept = '.json';
|
|
69
|
-
|
|
70
|
-
input.addEventListener('change', (event) => {
|
|
71
|
-
const file = event.target.files[0];
|
|
72
|
-
if (file) {
|
|
73
|
-
const reader = new FileReader();
|
|
74
|
-
reader.onload = () => {
|
|
75
|
-
try {
|
|
76
|
-
const jsonData = JSON.parse(reader.result);
|
|
77
|
-
jsonData.steps.forEach(step => {
|
|
78
|
-
let sm = new StepModel(step)
|
|
79
|
-
sm.setAsEdited();
|
|
80
|
-
});
|
|
81
|
-
reloadCropsFromJson(jsonData);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error("Error parsing JSON file:", error);
|
|
84
|
-
showJsonErrorModal(error.message);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
reader.readAsText(file);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
input.click();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function showJsonErrorModal(errorMessage) {
|
|
95
|
-
const errorModal = new bootstrap.Modal(document.getElementById('jsonErrorModal'));
|
|
96
|
-
document.getElementById('jsonErrorMessage').textContent = errorMessage;
|
|
97
|
-
errorModal.show();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function showConfirmationModal(onConfirm) {
|
|
101
|
-
|
|
102
|
-
if (crops?.steps.length === 0)
|
|
103
|
-
return onConfirm();
|
|
104
|
-
|
|
105
|
-
const confirmationModal = new bootstrap.Modal(document.getElementById('confirmationModal'));
|
|
106
|
-
const confirmButton = document.getElementById('confirmImport');
|
|
107
|
-
|
|
108
|
-
// Avoid multiple event listeners
|
|
109
|
-
const newConfirmButton = confirmButton.cloneNode(true);
|
|
110
|
-
confirmButton.replaceWith(newConfirmButton);
|
|
111
|
-
|
|
112
|
-
newConfirmButton.addEventListener('click', () => {
|
|
113
|
-
confirmationModal.hide();
|
|
114
|
-
onConfirm();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
confirmationModal.show();
|
|
118
|
-
}
|
package/react/QUICKSTART.md
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Quick Start - React/Next.js
|
|
2
|
-
|
|
3
|
-
Guide rapide pour intégrer `@osfarm/itineraire-technique` dans votre projet React ou Next.js.
|
|
4
|
-
|
|
5
|
-
## Installation (5 minutes)
|
|
6
|
-
|
|
7
|
-
### Étape 1 : Installer le package
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @osfarm/itineraire-technique
|
|
11
|
-
# ou
|
|
12
|
-
yarn add @osfarm/itineraire-technique
|
|
13
|
-
# ou
|
|
14
|
-
pnpm add @osfarm/itineraire-technique
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### Étape 2 : Copier les fichiers statiques
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm run setup:react
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Ce script copie automatiquement les fichiers JS, CSS et de test dans votre dossier `public/`.
|
|
24
|
-
|
|
25
|
-
### Étape 3 : Ajouter les dépendances CDN
|
|
26
|
-
|
|
27
|
-
**Pour Next.js App Router** : Créez ou modifiez `app/layout.tsx` :
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
31
|
-
return (
|
|
32
|
-
<html lang="fr">
|
|
33
|
-
<head>
|
|
34
|
-
<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.js"></script>
|
|
35
|
-
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
|
36
|
-
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-umd-min.js"></script>
|
|
37
|
-
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
38
|
-
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
|
39
|
-
|
|
40
|
-
<script src="/js/chart-render.js"></script>
|
|
41
|
-
<link href="/css/styles-rendering.css" rel="stylesheet" />
|
|
42
|
-
</head>
|
|
43
|
-
<body>{children}</body>
|
|
44
|
-
</html>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**Pour Next.js Pages Router** : Créez ou modifiez `pages/_document.tsx` (voir [exemple complet](../examples/nextjs-_document.tsx))
|
|
50
|
-
|
|
51
|
-
## Utilisation - Visualiseur Simple
|
|
52
|
-
|
|
53
|
-
Créez un composant pour afficher un itinéraire :
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
'use client'; // Pour Next.js App Router uniquement
|
|
57
|
-
|
|
58
|
-
import { TikaRenderer } from '@osfarm/itineraire-technique/react';
|
|
59
|
-
import { useEffect, useState } from 'react';
|
|
60
|
-
|
|
61
|
-
export default function MyItineraire() {
|
|
62
|
-
const [data, setData] = useState(null);
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
// Charger les données de test
|
|
66
|
-
fetch('/test/test.json')
|
|
67
|
-
.then(res => res.json())
|
|
68
|
-
.then(setData);
|
|
69
|
-
}, []);
|
|
70
|
-
|
|
71
|
-
if (!data) return <div>Chargement...</div>;
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className="container">
|
|
75
|
-
<h1>{data.title}</h1>
|
|
76
|
-
<TikaRenderer data={data} />
|
|
77
|
-
</div>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Utilisation - Éditeur Simple
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
'use client';
|
|
86
|
-
|
|
87
|
-
import { useItineraire } from '@osfarm/itineraire-technique/react';
|
|
88
|
-
import { TikaRenderer } from '@osfarm/itineraire-technique/react';
|
|
89
|
-
|
|
90
|
-
export default function MyEditor() {
|
|
91
|
-
const { data, addStep, exportToJson } = useItineraire({
|
|
92
|
-
title: "Ma rotation",
|
|
93
|
-
options: { view: "horizontal", show_transcript: true },
|
|
94
|
-
steps: []
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const handleAddCrop = () => {
|
|
98
|
-
addStep({
|
|
99
|
-
id: crypto.randomUUID(),
|
|
100
|
-
name: 'Nouvelle culture',
|
|
101
|
-
startDate: new Date().toISOString(),
|
|
102
|
-
endDate: new Date(Date.now() + 90*24*60*60*1000).toISOString(),
|
|
103
|
-
color: '#4CAF50',
|
|
104
|
-
interventions: []
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<div className="container">
|
|
110
|
-
<button className="btn btn-primary" onClick={handleAddCrop}>
|
|
111
|
-
Ajouter une culture
|
|
112
|
-
</button>
|
|
113
|
-
<button className="btn btn-secondary" onClick={() => console.log(exportToJson())}>
|
|
114
|
-
Exporter JSON
|
|
115
|
-
</button>
|
|
116
|
-
|
|
117
|
-
{data && <TikaRenderer data={data} />}
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## TypeScript Support
|
|
124
|
-
|
|
125
|
-
Types inclus ! Importez-les directement :
|
|
126
|
-
|
|
127
|
-
```tsx
|
|
128
|
-
import type { ItineraireData, Step, Intervention } from '@osfarm/itineraire-technique/react';
|
|
129
|
-
|
|
130
|
-
const myRotation: ItineraireData = {
|
|
131
|
-
title: "Ma rotation bio",
|
|
132
|
-
options: {
|
|
133
|
-
view: "horizontal",
|
|
134
|
-
show_transcript: true
|
|
135
|
-
},
|
|
136
|
-
steps: []
|
|
137
|
-
};
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## Exemples Complets
|
|
141
|
-
|
|
142
|
-
Consultez le dossier [`examples/`](../examples/) pour :
|
|
143
|
-
- ✅ Visualiseur Next.js App Router
|
|
144
|
-
- ✅ Éditeur Next.js App Router
|
|
145
|
-
- ✅ Configuration `_document.tsx`
|
|
146
|
-
- ✅ API Routes pour sauvegarder/charger des données
|
|
147
|
-
|
|
148
|
-
## Troubleshooting
|
|
149
|
-
|
|
150
|
-
### "RotationRenderer is not defined"
|
|
151
|
-
|
|
152
|
-
➡️ Vérifiez que `/js/chart-render.js` est bien chargé dans votre HTML/layout
|
|
153
|
-
➡️ Utilisez le hook `useItineraireDependencies` pour vérifier le chargement
|
|
154
|
-
|
|
155
|
-
### Les styles ne s'appliquent pas
|
|
156
|
-
|
|
157
|
-
➡️ Assurez-vous d'avoir chargé `/css/styles-rendering.css`
|
|
158
|
-
➡️ Vérifiez que Bootstrap CSS est aussi chargé
|
|
159
|
-
|
|
160
|
-
### Erreur "use client" manquant
|
|
161
|
-
|
|
162
|
-
➡️ Ajoutez `'use client';` en haut de vos composants qui utilisent des hooks React
|
|
163
|
-
|
|
164
|
-
## Documentation Complète
|
|
165
|
-
|
|
166
|
-
📚 [Documentation React complète](README.md)
|
|
167
|
-
|
|
168
|
-
## Besoin d'aide ?
|
|
169
|
-
|
|
170
|
-
- 🐛 [Signaler un bug](https://github.com/osfarm/itineraire-technique/issues)
|
|
171
|
-
- 💬 [Questions et discussions](https://github.com/osfarm/itineraire-technique/discussions)
|
|
172
|
-
- 📖 [Documentation Triple Performance](https://wiki.tripleperformance.fr/)
|