You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
7.1 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { collection, query, orderBy, onSnapshot, Timestamp, where, deleteDoc, doc, getDoc } from 'firebase/firestore';
import { Link } from 'react-router-dom';
import { db, handleFirestoreError, OperationType } from '../firebase';
import { useAuth } from '../components/AuthContext';
import { SynthesisDocument } from '../types';
import { format } from 'date-fns';
import { fr } from 'date-fns/locale';
import { FileText, Plus, Trash2, AlertTriangle } from 'lucide-react';
export default function Home() {
const { user } = useAuth();
const [syntheses, setSyntheses] = useState<SynthesisDocument[]>([]);
const [loading, setLoading] = useState(true);
const [deletingId, setDeletingId] = useState<string | null>(null);
const [maxItems, setMaxItems] = useState<number>(4);
useEffect(() => {
if (!user) return;
// Fetch settings to get maxItemsPerCategory
const fetchSettings = async () => {
try {
const settingsDoc = await getDoc(doc(db, 'settings', user.uid));
if (settingsDoc.exists()) {
const data = settingsDoc.data();
if (data.maxItemsPerCategory) {
setMaxItems(data.maxItemsPerCategory);
}
}
} catch (err) {
console.warn("Could not fetch settings", err);
}
};
fetchSettings();
const q = query(
collection(db, 'syntheses'),
where('authorUid', '==', user.uid)
);
const unsubscribe = onSnapshot(q, (snapshot) => {
const docs = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
})) as SynthesisDocument[];
// Sort client-side to avoid requiring a composite index
docs.sort((a, b) => b.createdAt.toMillis() - a.createdAt.toMillis());
setSyntheses(docs);
setLoading(false);
}, (error) => {
handleFirestoreError(error, OperationType.LIST, 'syntheses');
});
return () => unsubscribe();
}, [user]);
const handleDelete = async (e: React.MouseEvent, id: string) => {
e.preventDefault();
e.stopPropagation();
if (deletingId === id) {
// Confirm deletion
try {
await deleteDoc(doc(db, 'syntheses', id));
setDeletingId(null);
} catch (err) {
handleFirestoreError(err, OperationType.DELETE, `syntheses/${id}`);
setDeletingId(null);
}
} else {
// Show confirmation state
setDeletingId(id);
// Auto-cancel after 3 seconds
setTimeout(() => {
setDeletingId((current) => current === id ? null : current);
}, 3000);
}
};
if (loading) {
return (
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600"></div>
</div>
);
}
return (
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold text-gray-900">Synthèses d'Actualités par IA</h1>
<p className="mt-2 text-sm text-gray-500">
Retrouvez ici toutes vos synthèses hebdomadaires générées automatiquement.
</p>
</div>
<Link
to="/generate"
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<Plus className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
Nouvelle Synthèse
</Link>
</div>
{syntheses.length === 0 ? (
<div className="text-center py-12 bg-white rounded-lg shadow-sm border border-gray-200">
<FileText className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">Aucune synthèse</h3>
<p className="mt-1 text-sm text-gray-500">Commencez par générer votre première synthèse hebdomadaire.</p>
<div className="mt-6">
<Link
to="/generate"
className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700"
>
<Plus className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
Générer
</Link>
</div>
</div>
) : (
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{syntheses.map((synth) => (
<Link
key={synth.id}
to={`/synthesis/${synth.id}`}
className="block bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md hover:border-indigo-300 transition-all duration-200 overflow-hidden"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800">
Semaine {synth.week.split('-W')[1]}
</span>
<span className="text-sm text-gray-500">
{format(synth.createdAt.toDate(), 'dd MMM yyyy', { locale: fr })}
</span>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Synthèse de la semaine
</h3>
<div className="text-sm text-gray-600 space-y-1.5">
{(() => {
const items = synth.sections?.[0]?.items || synth.majorAnnouncements || [];
if (items.length === 0) {
return <p>Aucune annonce majeure cette semaine.</p>;
}
return items.slice(0, maxItems).map((item, idx) => (
<p key={idx} className="line-clamp-2">
• {item.title}
</p>
));
})()}
</div>
</div>
<div className="bg-gray-50 px-6 py-3 border-t border-gray-100 flex justify-between items-center">
<span className="text-sm font-medium text-indigo-600 hover:text-indigo-500">
Lire la synthèse &rarr;
</span>
<button
onClick={(e) => handleDelete(e, synth.id)}
className={`inline-flex items-center p-1.5 rounded-md transition-colors ${
deletingId === synth.id
? 'bg-red-100 text-red-700 hover:bg-red-200'
: 'text-gray-400 hover:text-red-600 hover:bg-red-50'
}`}
title={deletingId === synth.id ? "Cliquer à nouveau pour confirmer" : "Supprimer"}
>
{deletingId === synth.id ? (
<span className="text-xs font-medium mr-1">Confirmer</span>
) : null}
<Trash2 className="h-4 w-4" />
</button>
</div>
</Link>
))}
</div>
)}
</div>
);
}