AUDITPRO / components /AuditHistory.tsx
MMOON's picture
Upload AuditHistory.tsx
4042a5e verified
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 {
// UTILISATION DE text/plain POUR ÉVITER LE PREFLIGHT CORS
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();
// Si Google renvoie SUCCESS (voir le code Apps Script dans Settings)
if (resultText.includes("SUCCESS")) {
const updatedAudit = { ...audit, status: 'SENT' as const };
await saveToStore('audits', updatedAudit);
alert('Synchronisation réussie avec Google Sheets !');
} else {
// En cas d'erreur renvoyée par le script
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;