import React, { useState, useEffect } from 'react'; import { AuthProvider, AuthGuard, useAuth } from './AdminAuth'; import AdminLayout from './AdminLayout'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'; function FeedHealthContent() { const { token } = useAuth(); const [feeds, setFeeds] = useState([]); const [selectedFeed, setSelectedFeed] = useState(null); const [history, setHistory] = useState([]); const [loading, setLoading] = useState(true); const headers = { Authorization: `Bearer ${token}` }; useEffect(() => { fetch('/api/admin/feeds/health', { headers }) .then(r => r.json()) .then(res => setFeeds(res.feeds || [])) .finally(() => setLoading(false)); }, []); useEffect(() => { if (!selectedFeed) return; fetch(`/api/admin/feeds/health?feed_id=${selectedFeed}`, { headers }) .then(r => r.json()) .then(res => setHistory(res.history || [])); }, [selectedFeed]); const formatDuration = (d: any) => { if (!d) return '-'; const m = d.minutes || 0; const s = d.seconds || 0; return m > 0 ? `${m}m ${s}s` : `${s}s`; }; const timeAgo = (dt: string) => { if (!dt) return 'Nooit'; const diff = Date.now() - new Date(dt).getTime(); const h = Math.floor(diff / 3600000); if (h < 1) return `${Math.floor(diff / 60000)}m geleden`; if (h < 24) return `${h}u geleden`; return `${Math.floor(h / 24)}d geleden`; }; if (loading) return (
); return (
{feeds.map((f: any) => (
setSelectedFeed(selectedFeed === f.id ? null : f.id)}>
{f.name} {f.stale && Stale (>24h)} {f.high_errors && High Errors}
{f.product_count || 0} prod. {f.sneaker_ratio}% sneakers {timeAgo(f.last_sync_at)} {formatDuration(f.duration)} {f.last_sync_status || '-'}
{/* Expanded details */} {selectedFeed === f.id && (
Fetched: {f.products_fetched ?? '-'}
New: {f.products_new ?? '-'}
Updated: {f.products_updated ?? '-'}
Removed: {f.products_removed ?? '-'}
Errors: 0 ? 'text-[#FFD700]' : 'text-[#F5F5F5]'}>{f.errors_count ?? '-'}
{f.last_error && (
{f.last_error}
)} {/* Data quality tiles — per-feed */}
Data kwaliteit (sneakers in deze feed)
10 ? 'border-[#FF3B3B]/40' : Number(f.missing_model_pct) > 2 ? 'border-[#FFD700]/40' : 'border-[#333]'}`}>
Zonder model
{Number(f.missing_model).toLocaleString('nl-NL')} ({f.missing_model_pct}%)
5 ? 'border-[#FF3B3B]/40' : Number(f.missing_image_pct) > 1 ? 'border-[#FFD700]/40' : 'border-[#333]'}`}>
Zonder afbeelding
{Number(f.missing_image).toLocaleString('nl-NL')} ({f.missing_image_pct}%)
20 ? 'border-[#FF3B3B]/40' : Number(f.zero_price_offers) > 0 ? 'border-[#FFD700]/40' : 'border-[#333]'}`}>
€0 / null offers
{Number(f.zero_price_offers).toLocaleString('nl-NL')}
100 ? 'border-[#FF3B3B]/40' : Number(f.stale_products) > 20 ? 'border-[#FFD700]/40' : 'border-[#333]'}`}>
Stale (>30d)
{Number(f.stale_products).toLocaleString('nl-NL')}
{history.length > 0 && (

Sync History

)}
)}
))} {feeds.length === 0 && (
Geen feeds geconfigureerd
)}
); } export default function FeedHealth() { return ; }