| import React, { useState } from 'react'; |
| import { Audit } from '../types'; |
| import { |
| CloudUpload, |
| Trash2, |
| Download, |
| FileText, |
| CheckCircle2, |
| Loader2, |
| Sparkles, |
| AlertCircle, |
| X |
| } from 'lucide-react'; |
| import { saveToStore } from '../db'; |
| import { GoogleGenAI } from "@google/genai"; |
|
|
| interface AuditHistoryProps { |
| audits: Audit[]; |
| onDelete: (id: string) => void; |
| onView: (audit: Audit) => void; |
| } |
|
|
| const AuditHistory: React.FC<AuditHistoryProps> = ({ audits, onDelete, onView }) => { |
| const [syncingId, setSyncingId] = useState<string | null>(null); |
| const [analyzingId, setAnalyzingId] = useState<string | null>(null); |
| const [aiReport, setAiReport] = useState<{title: string, content: string} | null>(null); |
|
|
| const generateAiSummary = async (audit: Audit) => { |
| setAnalyzingId(audit.id); |
| try { |
| const ai = new GoogleGenAI({ apiKey: process.env.API_KEY || '' }); |
| const prompt = `En tant qu'expert en audit, analyse ces résultats d'audit pour le modèle "${audit.checklistName}". |
| Voici les réponses : ${JSON.stringify(audit.responses.map(r => ({ label: r.itemLabel, val: r.value, note: r.comment })))} |
| Donne un résumé exécutif très court, liste les 3 points critiques et suggère une action corrective prioritaire. Réponds en français.`; |
|
|
| const response = await ai.models.generateContent({ |
| model: 'gemini-3-flash-preview', |
| contents: prompt, |
| }); |
|
|
| setAiReport({ |
| title: `Analyse IA : ${audit.checklistName}`, |
| content: response.text || "Erreur de génération." |
| }); |
| } catch (err) { |
| alert("L'IA n'est pas disponible pour le moment."); |
| console.error(err); |
| } finally { |
| setAnalyzingId(null); |
| } |
| }; |
|
|
| const sendToWebhook = async (audit: Audit) => { |
| if (!audit.webhookUrl) return alert('Configurez vos IDs Google dans les réglages.'); |
| |
| setSyncingId(audit.id); |
| try { |
| |
| const response = await fetch(audit.webhookUrl, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'text/plain;charset=utf-8' |
| }, |
| body: JSON.stringify({ |
| event: 'audit_sync', |
| data: { ...audit, sheetId: audit.sheetId } |
| }) |
| }); |
| |
| const resultText = await response.text(); |
| |
| |
| if (resultText.includes("SUCCESS")) { |
| const updatedAudit = { ...audit, status: 'SENT' as const }; |
| await saveToStore('audits', updatedAudit); |
| alert('Synchronisation réussie avec Google Sheets !'); |
| } else { |
| |
| alert('Réponse Google : ' + resultText); |
| } |
| } catch (err) { |
| alert('Erreur de connexion. Vérifiez l\'URL de votre Web App Google.'); |
| console.error(err); |
| } finally { |
| setSyncingId(null); |
| } |
| }; |
|
|
| return ( |
| <div className="space-y-6 animate-in fade-in duration-500"> |
| <div className="flex items-center justify-between px-2"> |
| <h2 className="text-2xl font-black text-slate-800 tracking-tight">Historique</h2> |
| <span className="bg-slate-200 text-slate-600 text-[10px] font-black px-3 py-1 rounded-full uppercase tracking-widest">{audits.length} Audits</span> |
| </div> |
| |
| {aiReport && ( |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-sm animate-in fade-in"> |
| <div className="bg-white rounded-[2.5rem] shadow-2xl max-w-2xl w-full max-h-[80vh] overflow-hidden flex flex-col border border-slate-200"> |
| <div className="p-8 border-b border-slate-100 flex items-center justify-between bg-indigo-50/50"> |
| <div className="flex items-center gap-3 text-indigo-600"> |
| <Sparkles size={24} /> |
| <h3 className="font-black text-xl">{aiReport.title}</h3> |
| </div> |
| <button onClick={() => setAiReport(null)} className="p-2 hover:bg-white rounded-xl transition-all"> |
| <X size={24} /> |
| </button> |
| </div> |
| <div className="p-8 overflow-y-auto text-slate-700 leading-relaxed font-medium"> |
| <div className="prose prose-slate max-w-none"> |
| {aiReport.content.split('\n').map((line, i) => <p key={i} className="mb-4">{line}</p>)} |
| </div> |
| </div> |
| <div className="p-6 bg-slate-50 border-t border-slate-100 flex justify-end"> |
| <button onClick={() => setAiReport(null)} className="bg-slate-900 text-white px-8 py-3 rounded-2xl font-black text-sm">FERMER</button> |
| </div> |
| </div> |
| </div> |
| )} |
| |
| {audits.length === 0 ? ( |
| <div className="bg-white rounded-[2rem] p-16 border-2 border-dashed border-slate-200 text-center space-y-4"> |
| <FileText size={64} className="mx-auto text-slate-200" /> |
| <p className="text-slate-400 font-bold italic">Aucun audit trouvé.</p> |
| </div> |
| ) : ( |
| <div className="grid grid-cols-1 gap-4"> |
| {audits.map((audit) => ( |
| <div |
| key={audit.id} |
| className="bg-white border border-slate-200 rounded-[1.5rem] p-6 hover:shadow-xl transition-all flex flex-col md:flex-row gap-6 md:items-center justify-between group" |
| > |
| <div className="flex items-center gap-5"> |
| <div className={`p-4 rounded-2xl ${ |
| audit.status === 'SENT' ? 'bg-emerald-100 text-emerald-600' : |
| audit.status === 'COMPLETED' ? 'bg-amber-100 text-amber-600' : 'bg-blue-100 text-blue-600' |
| }`}> |
| {audit.status === 'SENT' ? <CheckCircle2 size={28} /> : <FileText size={28} />} |
| </div> |
| <div> |
| <h3 className="font-black text-slate-800 text-lg leading-tight">{audit.checklistName}</h3> |
| <div className="flex flex-wrap gap-x-4 gap-y-1 mt-1 text-[11px] font-bold text-slate-400"> |
| <p>{new Date(audit.startedAt).toLocaleString()}</p> |
| <p className="uppercase tracking-widest">Par {audit.inspectorName || 'Inconnu'}</p> |
| </div> |
| </div> |
| </div> |
| |
| <div className="flex items-center gap-2"> |
| <button |
| onClick={() => generateAiSummary(audit)} |
| disabled={analyzingId === audit.id} |
| className="flex items-center gap-2 px-4 py-2 bg-indigo-50 text-indigo-600 hover:bg-indigo-600 hover:text-white rounded-xl text-xs font-black transition-all shadow-sm" |
| > |
| {analyzingId === audit.id ? <Loader2 size={14} className="animate-spin" /> : <Sparkles size={14} />} |
| ANALYSE IA |
| </button> |
| <button |
| onClick={() => sendToWebhook(audit)} |
| disabled={syncingId === audit.id} |
| className={`flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-black transition-all ${ |
| audit.status === 'SENT' |
| ? 'bg-slate-100 text-slate-400' |
| : 'bg-indigo-600 text-white shadow-lg shadow-indigo-100' |
| }`} |
| > |
| {syncingId === audit.id ? <Loader2 size={14} className="animate-spin" /> : <CloudUpload size={14} />} |
| SYNC |
| </button> |
| <button |
| onClick={() => onDelete(audit.id)} |
| className="p-2 text-slate-300 hover:text-rose-500 hover:bg-rose-50 rounded-xl transition-all" |
| > |
| <Trash2 size={20} /> |
| </button> |
| </div> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| ); |
| }; |
|
|
| export default AuditHistory; |