feat: détection drift Sheets sur le dashboard (approche A)

- /api/dashboard/stats appelle Google Drive API pour récupérer modifiedTime du Sheets
- Compare avec createdAt de la dernière SyncRun → sheetDrift: boolean
- Dashboard affiche un bandeau amber cliquable si Sheets plus récent que la dernière sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:14:06 +02:00
parent 6f4affcd66
commit 1b07359415
2 changed files with 45 additions and 1 deletions
+18
View File
@@ -23,6 +23,8 @@ interface LastSync {
interface DashboardStats {
products: ProductStats
lastSync: LastSync | null
sheetDrift: boolean
sheetModifiedAt: string | null
}
function StatCard({
@@ -134,6 +136,22 @@ export default function DashboardPage() {
<div className="p-6 flex flex-col gap-8 flex-1 overflow-y-auto">
{/* ── Alerte drift Sheets ───────────────────────────────── */}
{!statsLoading && stats?.sheetDrift && (
<Link href="/sync" className="flex items-center gap-3 bg-amber-950/40 border border-amber-600/50 rounded-xl px-4 py-3 hover:bg-amber-950/60 transition-colors group">
<span className="text-xl"></span>
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold text-amber-300">Google Sheets modifié depuis la dernière sync</p>
<p className="text-xs text-amber-500 mt-0.5">
{stats.sheetModifiedAt
? `Dernière modification : ${formatDate(stats.sheetModifiedAt)}`
: 'Des changements sont peut-être en attente de synchronisation'}
</p>
</div>
<span className="text-xs text-amber-400 group-hover:text-amber-300 whitespace-nowrap">Lancer la sync </span>
</Link>
)}
{/* ── KPIs ─────────────────────────────────────────────── */}
<section>
<h2 className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">Vue d&apos;ensemble</h2>
+27 -1
View File
@@ -43,18 +43,36 @@ async function fetchProductsLight(status: 'active' | 'draft'): Promise<LightProd
return all
}
async function fetchSheetModifiedTime(): Promise<string | null> {
const apiKey = process.env.GOOGLE_API_KEY
const sheetId = process.env.GOOGLE_SHEETS_ID
if (!apiKey || !sheetId) return null
try {
const res = await fetch(
`https://www.googleapis.com/drive/v3/files/${sheetId}?fields=modifiedTime&key=${apiKey}`,
{ cache: 'no-store' },
)
if (!res.ok) return null
const data = await res.json()
return (data.modifiedTime as string) ?? null
} catch {
return null
}
}
export async function GET() {
const session = await getServerSession(authOptions)
if (!session) return NextResponse.json({ error: 'Non authentifié' }, { status: 401 })
try {
const [activeProducts, draftProducts, lastSyncRun] = await Promise.all([
const [activeProducts, draftProducts, lastSyncRun, sheetModifiedTime] = await Promise.all([
fetchProductsLight('active'),
fetchProductsLight('draft'),
prisma.syncRun.findFirst({
orderBy: { createdAt: 'desc' },
include: { user: { select: { name: true } } },
}),
fetchSheetModifiedTime(),
])
const allProducts = [...activeProducts, ...draftProducts]
@@ -72,6 +90,12 @@ export async function GET() {
}
: null
// Drift : Sheets modifié après la dernière sync réussie
const sheetDrift =
sheetModifiedTime !== null &&
lastSyncRun !== null &&
new Date(sheetModifiedTime) > lastSyncRun.createdAt
return NextResponse.json({
products: {
active: activeProducts.length,
@@ -80,6 +104,8 @@ export async function GET() {
withoutImages,
},
lastSync,
sheetDrift,
sheetModifiedAt: sheetModifiedTime,
})
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'