fix: DiffTable colonnes visibles + filtrage lignes parasites dans Sheets

- table-fixed avec colgroup pour éviter que la colonne Ref écrase les autres
- overflow-x-auto pour permettre le scroll si nécessaire
- parseSheetRows : ignore les refs invalides (< 2 chars, caractères spéciaux, mots réservés)
- parseSheetRows : ignore les lignes sans prix valide (séparateurs, notes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:09:08 +02:00
parent 211d8c5c01
commit 6f4affcd66
2 changed files with 27 additions and 10 deletions
+13 -8
View File
@@ -56,28 +56,33 @@ export function DiffTable({ diff }: DiffTableProps) {
{paginated.length === 0 ? ( {paginated.length === 0 ? (
<p className="text-slate-600 text-sm py-4 text-center">Aucun changement dans cette catégorie.</p> <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"> <div className="border border-slate-700 rounded-xl overflow-x-auto">
<table className="w-full text-xs"> <table className="w-full text-xs table-fixed">
<colgroup>
<col className="w-36" />
<col className="w-32" />
<col />
</colgroup>
<thead className="bg-slate-800 text-slate-400"> <thead className="bg-slate-800 text-slate-400">
<tr> <tr>
<th className="px-3 py-2 text-left">Ref</th> <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">Type</th>
<th className="px-3 py-2 text-left">Champs modifiés</th> <th className="px-3 py-2 text-left">Détail</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{paginated.map((change) => ( {paginated.map((change) => (
<tr key={change.ref} className={`border-t border-slate-800 ${TYPE_CONFIG[change.type].bg}`}> <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-mono text-slate-300 truncate max-w-0" title={change.ref}>{change.ref}</td>
<td className={`px-3 py-2 font-semibold ${TYPE_CONFIG[change.type].color}`}> <td className={`px-3 py-2 font-semibold whitespace-nowrap ${TYPE_CONFIG[change.type].color}`}>
{TYPE_CONFIG[change.type].label} {TYPE_CONFIG[change.type].label}
</td> </td>
<td className="px-3 py-2 text-slate-400"> <td className="px-3 py-2 text-slate-400">
{change.type === 'update' && change.changedFields?.map((f) => ( {change.type === 'update' && change.changedFields?.map((f) => (
<span key={f.field} className="inline-block mr-2"> <span key={f.field} className="inline-block mr-3">
<span className="text-slate-500">{f.field} : </span> <span className="text-slate-500">{f.field} : </span>
<span className="line-through text-red-400">{f.from}</span> <span className="line-through text-red-400 mr-1">{f.from}</span>
<span className="text-slate-400"> </span> <span className="text-slate-500 mr-1"></span>
<span className="text-green-400">{f.to}</span> <span className="text-green-400">{f.to}</span>
</span> </span>
))} ))}
+14 -2
View File
@@ -12,21 +12,33 @@ function normalizeHandle(ref: string): string {
* Colonnes : A=Référence, B=Nom, C=Description, D=Prix, E=Stock, F=Statut * Colonnes : A=Référence, B=Nom, C=Description, D=Prix, E=Stock, F=Statut
* Exporté pour les tests unitaires. * Exporté pour les tests unitaires.
*/ */
const HEADER_WORDS = new Set(['id', 'ref', 'sku', 'nom', 'titre', 'reference', 'prix', 'statut'])
function isValidRef(ref: string): boolean {
if (ref.length < 2) return false
if (/^[^a-zA-Z0-9]+$/.test(ref)) return false // que des caractères spéciaux
if (HEADER_WORDS.has(ref.toLowerCase())) return false
return true
}
export function parseSheetRows(rows: string[][]): SyncProduct[] { export function parseSheetRows(rows: string[][]): SyncProduct[] {
return rows return rows
.map((row): SyncProduct | null => { .map((row): SyncProduct | null => {
const ref = row[0]?.trim() const ref = row[0]?.trim()
if (!ref) return null if (!ref || !isValidRef(ref)) return null
const rawPrice = (row[3] ?? '').replace(',', '.').trim() const rawPrice = (row[3] ?? '').replace(',', '.').trim()
const price = parseFloat(rawPrice || '0') const price = parseFloat(rawPrice || '0')
// Ligne sans prix valide = séparateur ou note, pas un produit
if (isNaN(price) || price <= 0) return null
return { return {
ref, ref,
handle: normalizeHandle(ref), handle: normalizeHandle(ref),
title: row[1]?.trim() || ref, title: row[1]?.trim() || ref,
description: row[2]?.trim() || '', description: row[2]?.trim() || '',
price: isNaN(price) ? '0.00' : price.toFixed(2), price: price.toFixed(2),
stock: parseInt(row[4] ?? '0', 10) || 0, stock: parseInt(row[4] ?? '0', 10) || 0,
status: (row[5] ?? '').toLowerCase().includes('inac') ? 'draft' : 'active', status: (row[5] ?? '').toLowerCase().includes('inac') ? 'draft' : 'active',
} }