fix(ui): MetafieldResolver — défaut skip, UX claire create/map/ignore + labels
This commit is contained in:
@@ -27,8 +27,9 @@ export function MetafieldResolver({ specificColumns, onResolved }: Props) {
|
||||
setMatched(data.matched)
|
||||
setUnmatched(data.unmatched)
|
||||
const init: Record<string, MetafieldResolution> = { ...data.matched }
|
||||
// Défaut : 'skip' — l'utilisateur choisit explicitement ce qu'il veut créer
|
||||
data.unmatched.forEach((col: string) => {
|
||||
init[col] = { columnHeader: col, action: 'create', namespace: 'custom', key: normalizeMetafieldKey(col) }
|
||||
init[col] = { columnHeader: col, action: 'skip', namespace: 'custom', key: normalizeMetafieldKey(col) }
|
||||
})
|
||||
setResolutions(init)
|
||||
})
|
||||
@@ -69,47 +70,91 @@ export function MetafieldResolver({ specificColumns, onResolved }: Props) {
|
||||
)}
|
||||
{unmatched.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-yellow-400 mb-2">
|
||||
⚠ {unmatched.length} colonne{unmatched.length > 1 ? 's' : ''} sans correspondance — action requise
|
||||
</h3>
|
||||
<div className="mb-3 p-3 bg-slate-700/50 rounded-lg border border-slate-600">
|
||||
<p className="text-xs text-gray-400">
|
||||
Ces colonnes n'ont pas de métachamp correspondant dans Shopify.
|
||||
Choisissez pour chacune : <span className="text-indigo-300 font-medium">Créer nouveau</span> (nouveau métachamp Shopify),
|
||||
<span className="text-blue-300 font-medium"> Associer</span> (utiliser un existant), ou
|
||||
<span className="text-gray-300 font-medium"> Ignorer</span> (ne pas synchroniser).
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{unmatched.map(col => {
|
||||
const res = resolutions[col]
|
||||
if (!res) return null
|
||||
return (
|
||||
<div key={col} className="p-3 bg-slate-700 rounded-lg space-y-2">
|
||||
<p className="text-sm text-white font-medium">{col}</p>
|
||||
<div key={col} className={`p-3 rounded-lg space-y-2 border ${res.action === 'skip' ? 'bg-slate-700/40 border-slate-700' : res.action === 'create' ? 'bg-indigo-900/20 border-indigo-700/50' : 'bg-blue-900/20 border-blue-700/50'}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white font-medium">{col}</p>
|
||||
{res.action === 'create' && <span className="text-xs text-indigo-300">✓ Sera créé dans Shopify</span>}
|
||||
{res.action === 'map' && <span className="text-xs text-blue-300">✓ Associé à un existant</span>}
|
||||
{res.action === 'skip' && <span className="text-xs text-gray-500">— Ignoré</span>}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{(['create', 'map', 'skip'] as const).map(action => (
|
||||
<button
|
||||
key={action}
|
||||
type="button"
|
||||
onClick={() => updateResolution(col, { action })}
|
||||
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${res.action === action ? 'bg-indigo-600 text-white' : 'bg-slate-600 text-gray-300 hover:bg-slate-500'}`}
|
||||
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
|
||||
res.action === action
|
||||
? action === 'create' ? 'bg-indigo-600 text-white'
|
||||
: action === 'map' ? 'bg-blue-600 text-white'
|
||||
: 'bg-slate-500 text-white'
|
||||
: 'bg-slate-600 text-gray-400 hover:bg-slate-500 hover:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
{action === 'create' ? 'Créer nouveau' : action === 'map' ? 'Associer existant' : 'Ignorer'}
|
||||
{action === 'create' ? '+ Créer nouveau' : action === 'map' ? '↔ Associer existant' : '✕ Ignorer'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{res.action === 'create' && (
|
||||
<div className="flex gap-2">
|
||||
<input value={res.key} onChange={e => updateResolution(col, { key: e.target.value })} placeholder="clé (ex: puissance)"
|
||||
className="flex-1 bg-slate-600 border border-slate-500 rounded px-2 py-1 text-xs text-white" />
|
||||
<input value={res.namespace} onChange={e => updateResolution(col, { namespace: e.target.value })} placeholder="namespace"
|
||||
className="w-24 bg-slate-600 border border-slate-500 rounded px-2 py-1 text-xs text-white" />
|
||||
<div className="flex gap-2 mt-1">
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs text-gray-400 mb-1">Clé Shopify</label>
|
||||
<input
|
||||
type="text"
|
||||
value={res.key}
|
||||
onChange={e => updateResolution(col, { key: e.target.value })}
|
||||
placeholder="ex: puissance"
|
||||
className="w-full bg-slate-600 border border-slate-500 rounded px-2 py-1 text-xs text-white focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-28">
|
||||
<label className="block text-xs text-gray-400 mb-1">Namespace</label>
|
||||
<input
|
||||
type="text"
|
||||
value={res.namespace}
|
||||
onChange={e => updateResolution(col, { namespace: e.target.value })}
|
||||
placeholder="custom"
|
||||
className="w-full bg-slate-600 border border-slate-500 rounded px-2 py-1 text-xs text-white focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{res.action === 'map' && (
|
||||
<select value={res.existingDefinitionId ?? ''}
|
||||
onChange={e => { const def = definitions.find(d => d.id === e.target.value); if (def) updateResolution(col, { existingDefinitionId: def.id, key: def.key, namespace: def.namespace }) }}
|
||||
className="w-full bg-slate-600 border border-slate-500 rounded px-2 py-1 text-xs text-white">
|
||||
<option value="">-- Choisir un métachamp existant --</option>
|
||||
{definitions.map(d => <option key={d.id} value={d.id}>{d.name} ({d.namespace}.{d.key})</option>)}
|
||||
</select>
|
||||
<div>
|
||||
<label className="block text-xs text-gray-400 mb-1">Métachamp existant</label>
|
||||
<select
|
||||
value={res.existingDefinitionId ?? ''}
|
||||
onChange={e => { const def = definitions.find(d => d.id === e.target.value); if (def) updateResolution(col, { existingDefinitionId: def.id, key: def.key, namespace: def.namespace }) }}
|
||||
className="w-full bg-slate-600 border border-slate-500 rounded px-2 py-1 text-xs text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">-- Choisir un métachamp existant --</option>
|
||||
{definitions.map(d => <option key={d.id} value={d.id}>{d.name} ({d.namespace}.{d.key})</option>)}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{/* Résumé des actions */}
|
||||
<div className="mt-3 flex gap-4 text-xs text-gray-400">
|
||||
<span className="text-indigo-300 font-medium">{Object.values(resolutions).filter(r => r.action === 'create').length} à créer</span>
|
||||
<span className="text-blue-300 font-medium">{Object.values(resolutions).filter(r => r.action === 'map').length} à associer</span>
|
||||
<span>{Object.values(resolutions).filter(r => r.action === 'skip').length} ignorés</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user