feat: composant DiffTable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 22:06:53 +02:00
parent e6a670c67b
commit 9c3ec2a953
+118
View File
@@ -0,0 +1,118 @@
'use client'
import { useState } from 'react'
import type { SyncChange, SyncChangeType, DiffResult } from '@/types/sync'
interface DiffTableProps {
diff: DiffResult
}
const TYPE_CONFIG: Record<SyncChangeType, { label: string; color: string; bg: string }> = {
create: { label: '🟢 Nouveau', color: 'text-green-400', bg: 'bg-green-950/30' },
update: { label: '🔵 Mis à jour', color: 'text-blue-400', bg: 'bg-blue-950/30' },
deactivate: { label: '🔴 Désactivé', color: 'text-red-400', bg: 'bg-red-950/30' },
unchanged: { label: '⚫ Inchangé', color: 'text-slate-500', bg: '' },
}
const PAGE_SIZE = 20
export function DiffTable({ diff }: DiffTableProps) {
const [filter, setFilter] = useState<SyncChangeType | 'all'>('all')
const [page, setPage] = useState(0)
const visible = diff.changes.filter((c) => c.type !== 'unchanged')
const filtered = filter === 'all' ? visible : visible.filter((c) => c.type === filter)
const paginated = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
const totalPages = Math.ceil(filtered.length / PAGE_SIZE)
return (
<div className="flex flex-col gap-3">
{/* Résumé */}
<div className="flex gap-4 flex-wrap text-sm">
{(['create', 'update', 'deactivate', 'unchanged'] as SyncChangeType[]).map((type) => (
<span key={type} className={TYPE_CONFIG[type].color}>
{TYPE_CONFIG[type].label} : {diff.summary[type]}
</span>
))}
</div>
{/* Filtres */}
<div className="flex gap-2 flex-wrap">
{(['all', 'create', 'update', 'deactivate'] as const).map((f) => (
<button
key={f}
onClick={() => { setFilter(f); setPage(0) }}
className={`px-3 py-1 text-xs rounded-full border transition-colors ${
filter === f
? 'bg-indigo-600 border-indigo-500 text-white'
: 'border-slate-700 text-slate-400 hover:text-slate-200'
}`}
>
{f === 'all' ? 'Tous' : TYPE_CONFIG[f].label}
</button>
))}
</div>
{/* Tableau */}
{paginated.length === 0 ? (
<p className="text-slate-600 text-sm py-4 text-center">Aucun changement dans cette catégorie.</p>
) : (
<div className="border border-slate-700 rounded-xl overflow-hidden">
<table className="w-full text-xs">
<thead className="bg-slate-800 text-slate-400">
<tr>
<th className="px-3 py-2 text-left">Ref</th>
<th className="px-3 py-2 text-left">Type</th>
<th className="px-3 py-2 text-left">Champs modifiés</th>
</tr>
</thead>
<tbody>
{paginated.map((change) => (
<tr key={change.ref} className={`border-t border-slate-800 ${TYPE_CONFIG[change.type].bg}`}>
<td className="px-3 py-2 font-mono text-slate-300">{change.ref}</td>
<td className={`px-3 py-2 font-semibold ${TYPE_CONFIG[change.type].color}`}>
{TYPE_CONFIG[change.type].label}
</td>
<td className="px-3 py-2 text-slate-400">
{change.type === 'update' && change.changedFields?.map((f) => (
<span key={f.field} className="inline-block mr-2">
<span className="text-slate-500">{f.field} : </span>
<span className="line-through text-red-400">{f.from}</span>
<span className="text-slate-400"> </span>
<span className="text-green-400">{f.to}</span>
</span>
))}
{change.type === 'create' && (
<span className="text-green-400">{change.sheetProduct?.title}</span>
)}
{change.type === 'deactivate' && (
<span className="text-red-400">{change.shopifyProduct?.title}</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* Pagination */}
{totalPages > 1 && (
<div className="flex gap-2 justify-end">
<button
onClick={() => setPage((p) => Math.max(0, p - 1))}
disabled={page === 0}
className="px-3 py-1 text-xs bg-slate-800 rounded disabled:opacity-40 text-slate-300"
> Préc</button>
<span className="text-xs text-slate-500 self-center">
{page + 1} / {totalPages}
</span>
<button
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
disabled={page >= totalPages - 1}
className="px-3 py-1 text-xs bg-slate-800 rounded disabled:opacity-40 text-slate-300"
>Suiv </button>
</div>
)}
</div>
)
}