feat(plan4): composants ProductFiltersBar + ProductTable

This commit is contained in:
2026-06-08 23:26:01 +02:00
parent 5825640105
commit 119123e006
2 changed files with 118 additions and 0 deletions
@@ -0,0 +1,38 @@
'use client'
import type { ProductFilters } from '@/types/products'
interface Props {
filters: ProductFilters
total: number
onChange: (f: Partial<ProductFilters>) => void
}
export function ProductFiltersBar({ filters, total, onChange }: Props) {
return (
<div className="flex flex-wrap gap-3 items-center mb-4">
<input
type="text"
placeholder="Rechercher ref ou titre..."
value={filters.search}
onChange={(e) => onChange({ search: e.target.value })}
className="flex-1 min-w-48 bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
<div className="flex gap-2">
{(['all', 'active', 'draft'] as const).map((s) => (
<button
key={s}
onClick={() => onChange({ status: s })}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
filters.status === s
? 'bg-indigo-600 text-white'
: 'bg-slate-700 text-gray-300 hover:bg-slate-600'
}`}
>
{s === 'all' ? 'Tous' : s === 'active' ? 'Actifs' : 'Inactifs'}
</button>
))}
</div>
<span className="text-sm text-gray-400 ml-auto">{total} produit{total > 1 ? 's' : ''}</span>
</div>
)
}
+80
View File
@@ -0,0 +1,80 @@
'use client'
import type { CatalogProduct } from '@/types/products'
interface Props {
products: CatalogProduct[]
loading: boolean
}
const PAGE_SIZE = 50
export function ProductTable({ products, loading }: Props) {
if (loading) {
return (
<div className="flex items-center justify-center py-20 text-gray-400">
Chargement des produits
</div>
)
}
if (products.length === 0) {
return (
<div className="text-center py-20 text-gray-400 text-sm">
Aucun produit trouvé.
</div>
)
}
const displayed = products.slice(0, PAGE_SIZE)
return (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-left text-gray-400 border-b border-slate-700">
<th className="pb-3 pr-4 font-medium">Référence</th>
<th className="pb-3 pr-4 font-medium">Titre</th>
<th className="pb-3 pr-4 font-medium">Prix</th>
<th className="pb-3 pr-4 font-medium">Stock</th>
<th className="pb-3 pr-4 font-medium">Statut</th>
<th className="pb-3 font-medium">Lien</th>
</tr>
</thead>
<tbody>
{displayed.map((p) => (
<tr key={p.shopifyId} className="border-b border-slate-700/50 hover:bg-slate-700/30 transition-colors">
<td className="py-3 pr-4 font-mono text-indigo-300">{p.ref}</td>
<td className="py-3 pr-4 text-white">{p.title}</td>
<td className="py-3 pr-4 text-gray-300">{p.price} </td>
<td className="py-3 pr-4 text-gray-300">{p.stock}</td>
<td className="py-3 pr-4">
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
p.status === 'active'
? 'bg-green-900/50 text-green-300'
: 'bg-gray-700 text-gray-400'
}`}>
{p.status === 'active' ? 'Actif' : 'Inactif'}
</span>
</td>
<td className="py-3">
<a
href={p.shopifyUrl}
target="_blank"
rel="noopener noreferrer"
className="text-indigo-400 hover:text-indigo-300 text-xs underline"
>
Shopify
</a>
</td>
</tr>
))}
</tbody>
</table>
{products.length > PAGE_SIZE && (
<p className="text-center text-gray-400 text-xs mt-4">
Affichage des {PAGE_SIZE} premiers résultats sur {products.length}.
</p>
)}
</div>
)
}