feat: recherche textuelle dans la sélection de métachamp existant
Remplace le <select> par un input filtrant en temps réel sur le nom, la clé et le namespace — utile quand il y a beaucoup de métachamps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import type { MetafieldDefinition, MetafieldResolution } from '@/types/creation'
|
||||
import { normalizeMetafieldKey } from '@/lib/shopifyMetafields'
|
||||
|
||||
@@ -15,6 +15,9 @@ export function MetafieldResolver({ specificColumns, onResolved }: Props) {
|
||||
const [resolutions, setResolutions] = useState<Record<string, MetafieldResolution>>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [searches, setSearches] = useState<Record<string, string>>({})
|
||||
const [openDropdown, setOpenDropdown] = useState<string | null>(null)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (specificColumns.length === 0) { setLoading(false); onResolved([]); return }
|
||||
@@ -47,6 +50,16 @@ export function MetafieldResolver({ specificColumns, onResolved }: Props) {
|
||||
setResolutions(prev => ({ ...prev, [col]: { ...prev[col], ...partial } }))
|
||||
}
|
||||
|
||||
const filteredDefs = (col: string) => {
|
||||
const q = (searches[col] ?? '').toLowerCase()
|
||||
if (!q) return definitions
|
||||
return definitions.filter(d =>
|
||||
d.name.toLowerCase().includes(q) ||
|
||||
d.key.toLowerCase().includes(q) ||
|
||||
d.namespace.toLowerCase().includes(q)
|
||||
)
|
||||
}
|
||||
|
||||
if (loading) return <p className="text-gray-400 text-sm">Analyse des métachamps Shopify…</p>
|
||||
if (error) return <p className="text-red-400 text-sm">{error}</p>
|
||||
if (specificColumns.length === 0) return <p className="text-gray-400 text-sm">Aucune colonne spécifique détectée.</p>
|
||||
@@ -133,16 +146,45 @@ export function MetafieldResolver({ specificColumns, onResolved }: Props) {
|
||||
</div>
|
||||
)}
|
||||
{res.action === 'map' && (
|
||||
<div>
|
||||
<div className="relative" ref={openDropdown === col ? dropdownRef : undefined}>
|
||||
<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>
|
||||
{res.existingDefinitionId ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex-1 text-xs text-blue-300 bg-slate-600 border border-slate-500 rounded px-2 py-1 truncate">
|
||||
{definitions.find(d => d.id === res.existingDefinitionId)?.name ?? res.key} ({res.namespace}.{res.key})
|
||||
</span>
|
||||
<button type="button" onClick={() => { updateResolution(col, { existingDefinitionId: undefined, key: normalizeMetafieldKey(col), namespace: 'custom' }); setSearches(p => ({ ...p, [col]: '' })); setOpenDropdown(col) }} className="text-xs text-gray-400 hover:text-white px-1">✕</button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
value={searches[col] ?? ''}
|
||||
onChange={e => { setSearches(p => ({ ...p, [col]: e.target.value })); setOpenDropdown(col) }}
|
||||
onFocus={() => setOpenDropdown(col)}
|
||||
placeholder="Rechercher un métachamp…"
|
||||
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"
|
||||
/>
|
||||
{openDropdown === col && (
|
||||
<div className="absolute z-10 mt-1 w-full bg-slate-700 border border-slate-600 rounded shadow-lg max-h-48 overflow-y-auto">
|
||||
{filteredDefs(col).length === 0 ? (
|
||||
<p className="text-xs text-gray-400 px-3 py-2">Aucun résultat</p>
|
||||
) : filteredDefs(col).map(d => (
|
||||
<button
|
||||
key={d.id}
|
||||
type="button"
|
||||
onMouseDown={() => { updateResolution(col, { existingDefinitionId: d.id, key: d.key, namespace: d.namespace }); setOpenDropdown(null) }}
|
||||
className="w-full text-left px-3 py-1.5 text-xs text-white hover:bg-slate-600 flex justify-between gap-2"
|
||||
>
|
||||
<span>{d.name}</span>
|
||||
<span className="text-gray-400 shrink-0">{d.namespace}.{d.key}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user