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
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 →
|
|
</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>
|
|
);
|
|
}
|