Docfile commited on
Commit
640b8d2
·
verified ·
1 Parent(s): 3c0f013

Create gestion.html

Browse files
Files changed (1) hide show
  1. templates/gestion.html +448 -0
templates/gestion.html ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Gestion des Dissertations - Mariam AI</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
9
+ <style>
10
+ [x-cloak] { display: none !important; }
11
+ </style>
12
+ </head>
13
+ <body class="bg-gray-50 min-h-screen">
14
+ <div x-data="gestionApp()" x-init="loadData()" class="container mx-auto px-4 py-8">
15
+
16
+ <!-- Header -->
17
+ <div class="bg-white rounded-lg shadow-md p-6 mb-8">
18
+ <div class="flex justify-between items-center">
19
+ <div>
20
+ <h1 class="text-3xl font-bold text-gray-800">Gestion des Dissertations</h1>
21
+ <p class="text-gray-600 mt-2">Suivi des requêtes et réponses générées par Mariam AI</p>
22
+ </div>
23
+ <div class="flex space-x-4">
24
+ <a href="/" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition duration-200">
25
+ ← Retour à l'app
26
+ </a>
27
+ <button @click="confirmClearAll()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition duration-200">
28
+ Vider tout
29
+ </button>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <!-- Statistiques -->
35
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
36
+ <div class="bg-white p-6 rounded-lg shadow-md">
37
+ <div class="flex items-center">
38
+ <div class="p-3 rounded-full bg-blue-100 text-blue-500">
39
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
40
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
41
+ </svg>
42
+ </div>
43
+ <div class="ml-4">
44
+ <p class="text-sm font-medium text-gray-600">Total</p>
45
+ <p class="text-2xl font-semibold text-gray-900" x-text="stats.total"></p>
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+ <div class="bg-white p-6 rounded-lg shadow-md">
51
+ <div class="flex items-center">
52
+ <div class="p-3 rounded-full bg-green-100 text-green-500">
53
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
54
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
55
+ </svg>
56
+ </div>
57
+ <div class="ml-4">
58
+ <p class="text-sm font-medium text-gray-600">Succès</p>
59
+ <p class="text-2xl font-semibold text-gray-900" x-text="stats.success"></p>
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="bg-white p-6 rounded-lg shadow-md">
65
+ <div class="flex items-center">
66
+ <div class="p-3 rounded-full bg-red-100 text-red-500">
67
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
68
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
69
+ </svg>
70
+ </div>
71
+ <div class="ml-4">
72
+ <p class="text-sm font-medium text-gray-600">Erreurs</p>
73
+ <p class="text-2xl font-semibold text-gray-900" x-text="stats.errors"></p>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <div class="bg-white p-6 rounded-lg shadow-md">
79
+ <div class="flex items-center">
80
+ <div class="p-3 rounded-full bg-purple-100 text-purple-500">
81
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
83
+ </svg>
84
+ </div>
85
+ <div class="ml-4">
86
+ <p class="text-sm font-medium text-gray-600">Taux de succès</p>
87
+ <p class="text-2xl font-semibold text-gray-900" x-text="stats.successRate + '%'"></p>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+
93
+ <!-- Filtres -->
94
+ <div class="bg-white rounded-lg shadow-md p-6 mb-8">
95
+ <div class="flex flex-wrap gap-4 items-center">
96
+ <div>
97
+ <label class="block text-sm font-medium text-gray-700 mb-2">Filtrer par statut</label>
98
+ <select x-model="filter.status" @change="applyFilters()" class="border border-gray-300 rounded-md px-3 py-2">
99
+ <option value="">Tous</option>
100
+ <option value="success">Succès</option>
101
+ <option value="error">Erreurs</option>
102
+ </select>
103
+ </div>
104
+ <div>
105
+ <label class="block text-sm font-medium text-gray-700 mb-2">Filtrer par type</label>
106
+ <select x-model="filter.type" @change="applyFilters()" class="border border-gray-300 rounded-md px-3 py-2">
107
+ <option value="">Tous</option>
108
+ <option value="type1">Type 1</option>
109
+ <option value="type2">Type 2</option>
110
+ <option value="type3">Type 3</option>
111
+ </select>
112
+ </div>
113
+ <div>
114
+ <label class="block text-sm font-medium text-gray-700 mb-2">Rechercher</label>
115
+ <input type="text" x-model="filter.search" @input="applyFilters()"
116
+ placeholder="Rechercher dans les sujets..."
117
+ class="border border-gray-300 rounded-md px-3 py-2">
118
+ </div>
119
+ <div class="flex-1"></div>
120
+ <button @click="refreshData()" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition duration-200">
121
+ Actualiser
122
+ </button>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Messages -->
127
+ <div x-show="message.show" x-cloak
128
+ :class="message.type === 'success' ? 'bg-green-100 border-green-500 text-green-700' : 'bg-red-100 border-red-500 text-red-700'"
129
+ class="border-l-4 p-4 mb-6 rounded">
130
+ <p x-text="message.text"></p>
131
+ </div>
132
+
133
+ <!-- Loading -->
134
+ <div x-show="loading" x-cloak class="text-center py-8">
135
+ <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
136
+ <p class="mt-2 text-gray-600">Chargement...</p>
137
+ </div>
138
+
139
+ <!-- Liste des dissertations -->
140
+ <div x-show="!loading" x-cloak class="space-y-6">
141
+ <template x-for="item in filteredData" :key="item.id">
142
+ <div class="bg-white rounded-lg shadow-md p-6">
143
+ <div class="flex justify-between items-start mb-4">
144
+ <div class="flex-1">
145
+ <div class="flex items-center gap-3 mb-2">
146
+ <span class="text-sm text-gray-500" x-text="formatDate(item.timestamp)"></span>
147
+ <span :class="item.success ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'"
148
+ class="px-2 py-1 rounded-full text-xs font-medium"
149
+ x-text="item.success ? 'Succès' : 'Erreur'">
150
+ </span>
151
+ <span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs font-medium"
152
+ x-text="item.input.type">
153
+ </span>
154
+ </div>
155
+ <h3 class="text-lg font-semibold text-gray-800 mb-2" x-text="item.input.question"></h3>
156
+ </div>
157
+ <button @click="deleteRecord(item.id)"
158
+ class="bg-red-500 hover:bg-red-600 text-white px-3 py-1 rounded text-sm transition duration-200">
159
+ Supprimer
160
+ </button>
161
+ </div>
162
+
163
+ <!-- Détails de l'input -->
164
+ <div class="bg-gray-50 rounded-lg p-4 mb-4">
165
+ <h4 class="font-medium text-gray-700 mb-2">Données d'entrée</h4>
166
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
167
+ <div>
168
+ <span class="font-medium">Question:</span>
169
+ <p class="text-gray-600 mt-1" x-text="item.input.question"></p>
170
+ </div>
171
+ <div>
172
+ <span class="font-medium">Type:</span>
173
+ <p class="text-gray-600 mt-1" x-text="item.input.type"></p>
174
+ </div>
175
+ <div>
176
+ <span class="font-medium">Cours ID:</span>
177
+ <p class="text-gray-600 mt-1" x-text="item.input.courseId || 'Aucun'"></p>
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <!-- Résultat ou erreur -->
183
+ <div x-show="item.success" x-cloak>
184
+ <div class="border-l-4 border-green-500 bg-green-50 p-4 rounded">
185
+ <h4 class="font-medium text-green-800 mb-2">Résultat généré</h4>
186
+ <div class="space-y-2 text-sm">
187
+ <div><strong>Sujet:</strong> <span x-text="item.output?.sujet"></span></div>
188
+ <div><strong>Parties:</strong> <span x-text="item.output?.parties?.length + ' partie(s)'"></span></div>
189
+ <button @click="toggleDetails(item.id)"
190
+ class="text-green-600 hover:text-green-800 underline"
191
+ x-text="item.showDetails ? 'Masquer les détails' : 'Voir les détails'">
192
+ </button>
193
+
194
+ <!-- Détails complets -->
195
+ <div x-show="item.showDetails" x-cloak class="mt-4 p-4 bg-white rounded border">
196
+ <div class="space-y-4 max-h-96 overflow-y-auto">
197
+ <div>
198
+ <strong class="text-gray-700">Introduction:</strong>
199
+ <p class="mt-1 text-gray-600 text-sm" x-text="item.output?.introduction"></p>
200
+ </div>
201
+
202
+ <template x-for="(partie, index) in item.output?.parties" :key="index">
203
+ <div class="border-l-2 border-blue-200 pl-4">
204
+ <strong class="text-gray-700" x-text="'Partie ' + (index + 1) + ':'"></strong>
205
+ <p class="mt-1 text-gray-600 text-sm" x-text="partie.chapeau"></p>
206
+ <div class="mt-2">
207
+ <template x-for="(arg, argIndex) in partie.arguments" :key="argIndex">
208
+ <p class="mt-2 text-gray-600 text-sm pl-4 border-l border-gray-200"
209
+ x-text="arg.paragraphe_argumentatif"></p>
210
+ </template>
211
+ </div>
212
+ <p x-show="partie.transition" class="mt-2 text-gray-500 text-sm italic"
213
+ x-text="partie.transition"></p>
214
+ </div>
215
+ </template>
216
+
217
+ <div>
218
+ <strong class="text-gray-700">Conclusion:</strong>
219
+ <p class="mt-1 text-gray-600 text-sm" x-text="item.output?.conclusion"></p>
220
+ </div>
221
+ </div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+
227
+ <div x-show="!item.success" x-cloak>
228
+ <div class="border-l-4 border-red-500 bg-red-50 p-4 rounded">
229
+ <h4 class="font-medium text-red-800 mb-2">Erreur</h4>
230
+ <p class="text-red-700 text-sm" x-text="item.error"></p>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </template>
235
+
236
+ <!-- Message si aucune donnée -->
237
+ <div x-show="filteredData.length === 0" x-cloak class="text-center py-12">
238
+ <div class="text-gray-400 mb-4">
239
+ <svg class="mx-auto h-12 w-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
240
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
241
+ </svg>
242
+ </div>
243
+ <h3 class="text-lg font-medium text-gray-900">Aucune donnée trouvée</h3>
244
+ <p class="text-gray-500">Aucune dissertation n'a encore été générée ou ne correspond aux filtres.</p>
245
+ </div>
246
+ </div>
247
+
248
+ <!-- Modal de confirmation -->
249
+ <div x-show="showConfirmModal" x-cloak
250
+ class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
251
+ <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
252
+ <div class="mt-3 text-center">
253
+ <div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
254
+ <svg class="h-6 w-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
255
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.728-.833-2.498 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
256
+ </svg>
257
+ </div>
258
+ <h3 class="text-lg leading-6 font-medium text-gray-900 mt-2">Confirmer la suppression</h3>
259
+ <div class="mt-2 px-7 py-3">
260
+ <p class="text-sm text-gray-500">
261
+ Êtes-vous sûr de vouloir supprimer toutes les données ? Cette action est irréversible.
262
+ </p>
263
+ </div>
264
+ <div class="flex justify-center space-x-4 mt-4">
265
+ <button @click="showConfirmModal = false"
266
+ class="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400">
267
+ Annuler
268
+ </button>
269
+ <button @click="clearAllData()"
270
+ class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">
271
+ Supprimer tout
272
+ </button>
273
+ </div>
274
+ </div>
275
+ </div>
276
+ </div>
277
+
278
+ </div>
279
+
280
+ <script>
281
+ function gestionApp() {
282
+ return {
283
+ data: [],
284
+ filteredData: [],
285
+ loading: false,
286
+ showConfirmModal: false,
287
+ filter: {
288
+ status: '',
289
+ type: '',
290
+ search: ''
291
+ },
292
+ stats: {
293
+ total: 0,
294
+ success: 0,
295
+ errors: 0,
296
+ successRate: 0
297
+ },
298
+ message: {
299
+ show: false,
300
+ text: '',
301
+ type: 'success'
302
+ },
303
+
304
+ async loadData() {
305
+ this.loading = true;
306
+ try {
307
+ const response = await fetch('/api/gestion/dissertations');
308
+ const result = await response.json();
309
+
310
+ if (result.success) {
311
+ this.data = result.data;
312
+ this.calculateStats();
313
+ this.applyFilters();
314
+ } else {
315
+ this.showMessage('Erreur lors du chargement des données', 'error');
316
+ }
317
+ } catch (error) {
318
+ console.error('Erreur:', error);
319
+ this.showMessage('Erreur de connexion', 'error');
320
+ } finally {
321
+ this.loading = false;
322
+ }
323
+ },
324
+
325
+ async refreshData() {
326
+ await this.loadData();
327
+ this.showMessage('Données actualisées', 'success');
328
+ },
329
+
330
+ calculateStats() {
331
+ this.stats.total = this.data.length;
332
+ this.stats.success = this.data.filter(item => item.success).length;
333
+ this.stats.errors = this.data.filter(item => !item.success).length;
334
+ this.stats.successRate = this.stats.total > 0 ?
335
+ Math.round((this.stats.success / this.stats.total) * 100) : 0;
336
+ },
337
+
338
+ applyFilters() {
339
+ let filtered = [...this.data];
340
+
341
+ // Filtre par statut
342
+ if (this.filter.status) {
343
+ if (this.filter.status === 'success') {
344
+ filtered = filtered.filter(item => item.success);
345
+ } else if (this.filter.status === 'error') {
346
+ filtered = filtered.filter(item => !item.success);
347
+ }
348
+ }
349
+
350
+ // Filtre par type
351
+ if (this.filter.type) {
352
+ filtered = filtered.filter(item => item.input.type === this.filter.type);
353
+ }
354
+
355
+ // Filtre par recherche
356
+ if (this.filter.search) {
357
+ const searchLower = this.filter.search.toLowerCase();
358
+ filtered = filtered.filter(item =>
359
+ item.input.question.toLowerCase().includes(searchLower)
360
+ );
361
+ }
362
+
363
+ // Trier par date (plus récent d'abord)
364
+ filtered.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
365
+
366
+ this.filteredData = filtered;
367
+ },
368
+
369
+ async deleteRecord(id) {
370
+ if (!confirm('Êtes-vous sûr de vouloir supprimer cet enregistrement ?')) {
371
+ return;
372
+ }
373
+
374
+ try {
375
+ const response = await fetch(`/api/gestion/dissertations/${id}`, {
376
+ method: 'DELETE'
377
+ });
378
+ const result = await response.json();
379
+
380
+ if (result.success) {
381
+ await this.loadData();
382
+ this.showMessage('Enregistrement supprimé', 'success');
383
+ } else {
384
+ this.showMessage('Erreur lors de la suppression', 'error');
385
+ }
386
+ } catch (error) {
387
+ console.error('Erreur:', error);
388
+ this.showMessage('Erreur de connexion', 'error');
389
+ }
390
+ },
391
+
392
+ confirmClearAll() {
393
+ this.showConfirmModal = true;
394
+ },
395
+
396
+ async clearAllData() {
397
+ this.showConfirmModal = false;
398
+
399
+ try {
400
+ const response = await fetch('/api/gestion/dissertations/clear', {
401
+ method: 'DELETE'
402
+ });
403
+ const result = await response.json();
404
+
405
+ if (result.success) {
406
+ await this.loadData();
407
+ this.showMessage('Toutes les données ont été supprimées', 'success');
408
+ } else {
409
+ this.showMessage('Erreur lors de la suppression', 'error');
410
+ }
411
+ } catch (error) {
412
+ console.error('Erreur:', error);
413
+ this.showMessage('Erreur de connexion', 'error');
414
+ }
415
+ },
416
+
417
+ toggleDetails(id) {
418
+ const item = this.data.find(item => item.id === id);
419
+ if (item) {
420
+ item.showDetails = !item.showDetails;
421
+ }
422
+ },
423
+
424
+ formatDate(timestamp) {
425
+ const date = new Date(timestamp);
426
+ return date.toLocaleString('fr-FR', {
427
+ year: 'numeric',
428
+ month: '2-digit',
429
+ day: '2-digit',
430
+ hour: '2-digit',
431
+ minute: '2-digit'
432
+ });
433
+ },
434
+
435
+ showMessage(text, type = 'success') {
436
+ this.message.text = text;
437
+ this.message.type = type;
438
+ this.message.show = true;
439
+
440
+ setTimeout(() => {
441
+ this.message.show = false;
442
+ }, 3000);
443
+ }
444
+ }
445
+ }
446
+ </script>
447
+ </body>
448
+ </html>