feat: composant DiffTable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user