'use client'
import { useEffect, useState, useCallback } from 'react'
import Link from 'next/link'
import { Header } from '@/components/layout/Header'
interface ProductStats {
active: number
draft: number
total: number
withoutImages: number
}
interface LastSync {
createdAt: string
userName: string
status: string
created: number
updated: number
deactivated: number
errors: number
}
interface DashboardStats {
products: ProductStats
lastSync: LastSync | null
}
function StatCard({
icon,
label,
value,
sub,
href,
loading,
error,
accent,
}: {
icon: string
label: string
value: string | number | null
sub?: string
href?: string
loading?: boolean
error?: boolean
accent?: 'indigo' | 'amber' | 'green' | 'red'
}) {
const accentClass = {
indigo: 'border-indigo-500/30 bg-indigo-500/5',
amber: 'border-amber-500/30 bg-amber-500/5',
green: 'border-green-500/30 bg-green-500/5',
red: 'border-red-500/30 bg-red-500/5',
}[accent ?? 'indigo']
const content = (
{icon}
{label}
{loading ? (
β
) : error ? (
Erreur
) : (
{value ?? 'β'}
)}
{sub &&
{sub}
}
)
return href ? {content} : content
}
function ActionCard({ href, icon, label, description }: { href: string; icon: string; label: string; description: string }) {
return (
{icon}
)
}
function formatDate(iso: string) {
return new Intl.DateTimeFormat('fr-FR', {
day: '2-digit', month: 'short', year: 'numeric',
hour: '2-digit', minute: '2-digit',
}).format(new Date(iso))
}
export default function DashboardPage() {
const [stats, setStats] = useState(null)
const [statsError, setStatsError] = useState(false)
const [statsLoading, setStatsLoading] = useState(true)
const [pendingCount, setPendingCount] = useState(null)
const [pendingLoading, setPendingLoading] = useState(false)
const [pendingError, setPendingError] = useState(false)
useEffect(() => {
fetch('/api/dashboard/stats')
.then((r) => r.json())
.then((data) => {
if (data.error) throw new Error(data.error)
setStats(data)
})
.catch(() => setStatsError(true))
.finally(() => setStatsLoading(false))
}, [])
const loadPending = useCallback(async () => {
setPendingLoading(true)
setPendingError(false)
try {
const r = await fetch('/api/dashboard/pending')
const data = await r.json()
if (data.error) throw new Error(data.error)
setPendingCount(data.count)
} catch {
setPendingError(true)
} finally {
setPendingLoading(false)
}
}, [])
const lastSync = stats?.lastSync
return (
<>
{/* ββ KPIs βββββββββββββββββββββββββββββββββββββββββββββββ */}
Vue d'ensemble
0 ? 'amber' : 'green'}
href="/images"
/>
{/* Pending β chargement lazy */}
β¨
En attente de crΓ©ation
{pendingLoading ? (
β
) : pendingError ? (
Erreur β rΓ©essayer
) : pendingCount !== null ? (
{pendingCount}
) : (
)}
{pendingCount !== null && (
depuis Google Sheets
)}
{/* ββ DerniΓ¨re sync dΓ©tail ββββββββββββββββββββββββββββββββ */}
{!statsLoading && lastSync && (
Dernière synchronisation
{formatDate(lastSync.createdAt)}
par {lastSync.userName}
{lastSync.status === 'success' ? 'β
SuccΓ¨s' : 'β Erreur'}
{[
{ label: 'CrΓ©ations', value: lastSync.created, color: 'text-green-400' },
{ label: 'Mises Γ jour', value: lastSync.updated, color: 'text-blue-400' },
{ label: 'DΓ©sactivΓ©s', value: lastSync.deactivated, color: 'text-amber-400' },
{ label: 'Erreurs', value: lastSync.errors, color: 'text-red-400' },
].map(({ label, value, color }) => (
))}
Nouvelle sync β
)}
{/* ββ Actions rapides βββββββββββββββββββββββββββββββββββββ */}
>
)
}