import React, { useEffect, useState } from 'react'; import { AuthProvider, AuthGuard, useAuth } from './AdminAuth'; import AdminLayout from './AdminLayout'; type Row = Record; interface Payload { summary: Record; missing_model: Row[]; missing_image: Row[]; no_offers_sample: Row[]; zero_price_sample: Row[]; duplicate_ean: Row[]; singleton_models: Row[]; stale_products_by_brand: Row[]; tiny_brands: Row[]; error?: string; } function Tile({ label, value, tone = 'neutral', hint }: { label: string; value: number | string; tone?: 'neutral' | 'warn' | 'bad' | 'ok'; hint?: string }) { const toneClass = tone === 'bad' ? 'border-[#FF3B3B]/40 text-[#FF3B3B]' : tone === 'warn' ? 'border-[#FFD700]/40 text-[#FFD700]' : tone === 'ok' ? 'border-[#00FF6A]/40 text-[#00FF6A]' : 'border-[#333] text-[#F5F5F5]'; return (
{label}
{value}
{hint &&
{hint}
}
); } function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) { return (

{title}

{description &&

{description}

}
{children}
); } function Table({ headers, rows, empty = 'Geen rijen' }: { headers: string[]; rows: (string | number)[][]; empty?: string }) { if (!rows.length) return
{empty}
; return (
{headers.map((h, i) => )} {rows.map((r, i) => ( {r.map((c, j) => )} ))}
{h}
{c}
); } function DataQualityContent() { const { token } = useAuth(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [err, setErr] = useState(null); useEffect(() => { fetch('/api/admin/data-quality', { headers: { Authorization: `Bearer ${token}` } }) .then(r => r.json()) .then((res: Payload) => { if (res.error) setErr(res.error); else setData(res); }) .catch(e => setErr(e.message)) .finally(() => setLoading(false)); }, []); if (loading) return (
); if (err || !data) return (
Fout: {err || 'Geen data'}
); const s = data.summary; const tot = Number(s.total_sneakers) || 1; const pct = (n: number) => `${((Number(n) / tot) * 100).toFixed(1)}%`; return (
{/* Summary tiles */}
0.1 ? 'bad' : 'warn'} hint={pct(s.missing_model) + ' van totaal'} /> 0.05 ? 'bad' : 'warn'} hint={pct(s.missing_image) + ' van totaal'} /> 0.1 ? 'bad' : 'warn'} hint={pct(s.no_offers) + ' (offline)'} /> 50 ? 'bad' : 'warn'} /> 100 ? 'warn' : 'neutral'} hint="groepen" /> 0.2 ? 'bad' : 'warn'} />
{/* Missing model breakdown */}
[r.brand || '(leeg)', Number(r.cnt).toLocaleString('nl-NL')])} /> {/* Missing image */}
[r.brand || '(leeg)', Number(r.cnt).toLocaleString('nl-NL')])} /> {/* No offers */}
[r.id, r.brand || '-', r.name || '-', r.slug || '-'])} /> {/* Zero price */}
[r.id, r.brand || '-', r.name || '-', Number(r.bad_offers)])} /> {/* Duplicate EANs */}
[r.ean, Number(r.cnt), String(r.titles).slice(0, 120)])} /> {/* Singleton models */}
[r.brand || '-', r.model || '-', Number(r.cnt)])} /> {/* Stale by brand */}
[ r.brand || '-', Number(r.cnt).toLocaleString('nl-NL'), r.oldest ? new Date(r.oldest).toLocaleDateString('nl-NL') : '-', ])} /> {/* Tiny brands */}
[r.brand || '-', Number(r.cnt)])} /> ); } export default function DataQuality() { return ; }