mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-12 01:36:54 +02:00
feat(analytics): resilient Graph API calls, CRON sync every 6h, polling frontend, engagement+views metrics
This commit is contained in:
1 parent
942e33df62
commit
330c42c6ff
5 files changed
+130
-55
No files matched your search
@@ -1,19 +1,23 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { MetricsCard } from './MetricsCard';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RefreshCw, Users, BarChart3, Eye } from 'lucide-react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||||
import { RefreshCw, Users, BarChart3, Eye, Heart } from 'lucide-react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
||||
import { format } from 'date-fns';
|
||||
import { apiRequest } from '@/lib/queryClient';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
/** Maximum staleness before triggering an auto-refresh (6 hours in ms). */
|
||||
const STALE_THRESHOLD_MS = 6 * 60 * 60 * 1000;
|
||||
|
||||
export function AnalyticsDashboard() {
|
||||
const [selectedPageId, setSelectedPageId] = useState<string | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const { toast } = useToast();
|
||||
const autoRefreshDone = useRef(false);
|
||||
|
||||
// Fetch Pages
|
||||
const { data: pages = [], isLoading: isLoadingPages } = useQuery<any[]>({
|
||||
@@ -27,10 +31,11 @@ export function AnalyticsDashboard() {
|
||||
}
|
||||
}, [pages, selectedPageId]);
|
||||
|
||||
// Fetch History for selected page
|
||||
// Fetch History for selected page (with polling every 30s)
|
||||
const { data: history = [], isLoading: isLoadingHistory } = useQuery<any[]>({
|
||||
queryKey: [`/api/analytics/pages/${selectedPageId}/history`],
|
||||
enabled: !!selectedPageId,
|
||||
refetchInterval: 30_000, // Poll every 30 seconds
|
||||
});
|
||||
|
||||
// Refresh Mutation
|
||||
@@ -56,20 +61,35 @@ export function AnalyticsDashboard() {
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-refresh on first load if data is stale or empty
|
||||
useEffect(() => {
|
||||
if (!selectedPageId || autoRefreshDone.current || refreshMutation.isPending) return;
|
||||
|
||||
const isStale = history.length === 0
|
||||
|| (history[0]?.date && (Date.now() - new Date(history[0].date).getTime()) > STALE_THRESHOLD_MS);
|
||||
|
||||
if (isStale) {
|
||||
autoRefreshDone.current = true;
|
||||
refreshMutation.mutate();
|
||||
}
|
||||
}, [selectedPageId, history, refreshMutation]);
|
||||
|
||||
const selectedPage = pages.find((p: any) => p.id === selectedPageId);
|
||||
|
||||
// Prepare chart data (reverse to show chronological)
|
||||
// Prepare chart data (reverse to show chronological order)
|
||||
const chartData = [...history].reverse().map((entry: any) => ({
|
||||
date: format(new Date(entry.date), 'MM/dd'),
|
||||
date: format(new Date(entry.date), 'dd/MM'),
|
||||
followers: entry.followersCount,
|
||||
reach: entry.pageReach
|
||||
reach: entry.pageReach,
|
||||
engagement: entry.pageEngagement || 0,
|
||||
views: entry.pageViews || 0,
|
||||
}));
|
||||
|
||||
// Calculate trends (simple comparison with previous entry)
|
||||
// Calculate trends (comparison between latest and previous entry)
|
||||
const latest = history[0];
|
||||
const previous = history[1];
|
||||
|
||||
const getTrend = (current: number, prev: number) => {
|
||||
const getTrend = (current: number, prev: number): number => {
|
||||
if (!prev) return 0;
|
||||
return Math.round(((current - prev) / prev) * 100);
|
||||
};
|
||||
@@ -126,14 +146,27 @@ export function AnalyticsDashboard() {
|
||||
value={latest?.pageReach?.toLocaleString() || 0}
|
||||
icon={<BarChart3 className="h-4 w-4 text-muted-foreground" />}
|
||||
trend={latest && previous ? getTrend(latest.pageReach, previous.pageReach) : undefined}
|
||||
description="depuis la dernière mise à jour"
|
||||
description="impressions uniques (jour)"
|
||||
/>
|
||||
<MetricsCard
|
||||
title="Engagement"
|
||||
value={(latest?.pageEngagement || 0).toLocaleString()}
|
||||
icon={<Heart className="h-4 w-4 text-muted-foreground" />}
|
||||
trend={latest && previous ? getTrend(latest.pageEngagement || 0, previous.pageEngagement || 0) : undefined}
|
||||
description="interactions totales (jour)"
|
||||
/>
|
||||
<MetricsCard
|
||||
title="Vues de Page"
|
||||
value={(latest?.pageViews || 0).toLocaleString()}
|
||||
icon={<Eye className="h-4 w-4 text-muted-foreground" />}
|
||||
trend={latest && previous ? getTrend(latest.pageViews || 0, previous.pageViews || 0) : undefined}
|
||||
description="vues totales (jour)"
|
||||
/>
|
||||
{/* Add more metrics if available */}
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Historique de Croissance & Portée</CardTitle>
|
||||
<CardTitle>Historique de Croissance & Portée</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[300px]">
|
||||
@@ -144,8 +177,11 @@ export function AnalyticsDashboard() {
|
||||
<YAxis yAxisId="left" />
|
||||
<YAxis yAxisId="right" orientation="right" />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line yAxisId="left" type="monotone" dataKey="followers" stroke="#8884d8" name="Abonnés" />
|
||||
<Line yAxisId="right" type="monotone" dataKey="reach" stroke="#82ca9d" name="Portée" />
|
||||
<Line yAxisId="right" type="monotone" dataKey="engagement" stroke="#ff7f50" name="Engagement" />
|
||||
<Line yAxisId="right" type="monotone" dataKey="views" stroke="#ffa500" name="Vues" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user