gpt-engineer-app[bot] hace 1 semana
padre
commit
346e613ecf

+ 23 - 2
src/components/NewsCard.tsx

@@ -2,11 +2,26 @@ import { NewsItem } from '@/types/news';
 import { Card, CardContent, CardHeader } from '@/components/ui/card';
 import { Badge } from '@/components/ui/badge';
 import { Button } from '@/components/ui/button';
-import { Clock, Pin, ExternalLink, Eye, Trash2, Copy, Rss, Youtube, Gamepad2, Newspaper } from 'lucide-react';
+import { Clock, Pin, ExternalLink, Eye, Trash2, Copy, Rss, Youtube, Gamepad2, Newspaper, Radio } from 'lucide-react';
 import { cn } from '@/lib/utils';
 import { useAuth } from '@/hooks/useAuth';
 import { decodeHtmlEntities } from '@/utils/htmlDecode';
 
+const formatRelativeTime = (dateString: string): string => {
+  const date = new Date(dateString);
+  const now = new Date();
+  const diffMs = now.getTime() - date.getTime();
+  const diffMins = Math.floor(diffMs / 60000);
+  const diffHours = Math.floor(diffMins / 60);
+  const diffDays = Math.floor(diffHours / 24);
+
+  if (diffMins < 1) return "à l'instant";
+  if (diffMins < 60) return `il y a ${diffMins}min`;
+  if (diffHours < 24) return `il y a ${diffHours}h`;
+  if (diffDays < 7) return `il y a ${diffDays}j`;
+  return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
+};
+
 interface NewsCardProps {
   news: NewsItem;
   onTogglePin: (id: string) => void;
@@ -134,7 +149,7 @@ const NewsCard = ({
         </div>
         
         <div className="flex items-center justify-between pt-2">
-          <div className="flex items-center gap-2 text-xs text-muted-foreground">
+          <div className="flex items-center gap-2 text-xs text-muted-foreground flex-wrap">
             <Badge 
               variant="outline" 
               className={cn(
@@ -158,6 +173,12 @@ const NewsCard = ({
                 minute: '2-digit'
               })}
             </span>
+            {news.lastSeenAt && (
+              <span className="flex items-center gap-1 text-emerald-600 dark:text-emerald-400">
+                <Radio className="h-3 w-3" />
+                Vu {formatRelativeTime(news.lastSeenAt)}
+              </span>
+            )}
           </div>
           
           <div className="flex items-center gap-2">

+ 2 - 1
src/hooks/useFeedArticles.tsx

@@ -93,7 +93,8 @@ export function useFeedArticles(feedId: string, page: number = 1) {
           isRead: user ? (article.user_articles[0]?.is_read || false) : false,
           url: article.url || undefined,
           imageUrl: article.image_url || undefined,
-          feedId: article.feed_id
+          feedId: article.feed_id,
+          lastSeenAt: article.last_seen_at || undefined
         })) || [];
 
       setArticles(transformedArticles.slice(0, ARTICLES_PER_PAGE));

+ 6 - 3
src/hooks/useRealArticles.tsx

@@ -139,7 +139,8 @@ export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showF
             isRead: article.user_articles?.[0]?.is_read || false,
             url: article.url || undefined,
             imageUrl: article.image_url || undefined,
-            feedId: article.feed_id
+            feedId: article.feed_id,
+            lastSeenAt: article.last_seen_at || undefined
           })) || [];
 
         setArticles(transformedArticles.slice(0, 100));
@@ -198,7 +199,8 @@ export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showF
           isRead: article.user_articles?.[0]?.is_read || false,
           feedId: article.feed_id,
           readTime: article.read_time || 5,
-          isDiscovery: true
+          isDiscovery: true,
+          lastSeenAt: article.last_seen_at || undefined
         }));
         
         setArticles(formattedArticles);
@@ -277,7 +279,8 @@ export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showF
             isRead: user ? (article.user_articles?.[0]?.is_read || false) : false,
             url: article.url || undefined,
             imageUrl: article.image_url || undefined,
-            feedId: article.feed_id
+            feedId: article.feed_id,
+            lastSeenAt: article.last_seen_at || undefined
            })) || [];
 
         setArticles(transformedArticles.slice(0, 100));

+ 3 - 0
src/integrations/supabase/types.ts

@@ -23,6 +23,7 @@ export type Database = {
           guid: string | null
           id: string
           image_url: string | null
+          last_seen_at: string | null
           published_at: string
           read_time: number | null
           title: string
@@ -37,6 +38,7 @@ export type Database = {
           guid?: string | null
           id?: string
           image_url?: string | null
+          last_seen_at?: string | null
           published_at: string
           read_time?: number | null
           title: string
@@ -51,6 +53,7 @@ export type Database = {
           guid?: string | null
           id?: string
           image_url?: string | null
+          last_seen_at?: string | null
           published_at?: string
           read_time?: number | null
           title?: string

+ 1 - 0
src/types/news.ts

@@ -14,6 +14,7 @@ export interface NewsItem {
   imageUrl?: string;
   feedId?: string;
   isDiscovery?: boolean;
+  lastSeenAt?: string;
 }
 
 export interface NewsCategory {

+ 5 - 4
supabase/functions/fetch-rss/index.ts

@@ -152,6 +152,7 @@ serve(async (req) => {
     console.log(`Parsed ${items.length} items from RSS feed`)
 
     // Save articles to database
+    const now = new Date().toISOString();
     const articlesToInsert = items.map(item => {
       // Calculate read time (rough estimate: 200 words per minute)
       const wordCount = (item.description || '').split(' ').length
@@ -179,18 +180,18 @@ serve(async (req) => {
         image_url: item.image || null,
         published_at: publishedAt,
         guid: item.guid,
-        read_time: readTime
+        read_time: readTime,
+        last_seen_at: now
       }
     })
 
     console.log(`Preparing to insert ${articlesToInsert.length} articles`)
 
-    // Insert articles (on conflict do nothing to avoid duplicates)
+    // Insert articles - on conflict update last_seen_at
     const { error: insertError } = await supabaseClient
       .from('articles')
       .upsert(articlesToInsert, { 
-        onConflict: 'feed_id,guid',
-        ignoreDuplicates: true 
+        onConflict: 'feed_id,guid'
       })
 
     if (insertError) {

+ 8 - 0
supabase/migrations/20251216143609_43b79e5f-32e2-43c5-ab31-e376f65eda1d.sql

@@ -0,0 +1,8 @@
+-- Ajouter la colonne last_seen_at pour suivre quand un article a été vu dans le flux RSS
+ALTER TABLE public.articles 
+ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW();
+
+-- Mettre à jour les articles existants avec leur date de création comme valeur initiale
+UPDATE public.articles 
+SET last_seen_at = created_at 
+WHERE last_seen_at IS NULL;