Checkpoint: Zone de filtres Factures BAP refactorisée en 3 lignes dans un cartouche vert émeraude pastel : ligne 1 (recherche), ligne 2 (filtres période année/mois), ligne 3 (boutons statut + tri aligné à droite).
This commit is contained in:
@@ -714,146 +714,151 @@ export default function InvoicesBAP() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
{/* Search */}
|
||||
<div className="mb-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<Input
|
||||
placeholder="Rechercher par fournisseur ou numéro..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* ===== CARTOUCHE FILTRES BAP ===== */}
|
||||
<div className="rounded-xl border border-emerald-100 bg-emerald-50/60 px-4 py-3 mb-4 shadow-sm">
|
||||
|
||||
{/* Period Filter + Status Filters */}
|
||||
<div className="flex flex-wrap items-center gap-2 mb-3 p-3 bg-slate-50 dark:bg-slate-900/50 rounded-lg border border-slate-200 dark:border-slate-700">
|
||||
<div className="flex items-center gap-1.5 text-sm font-medium text-slate-600 dark:text-slate-400">
|
||||
<CalendarDays className="w-4 h-4" />
|
||||
Période :
|
||||
</div>
|
||||
{/* Year selector */}
|
||||
<Select value={selectedYear} onValueChange={(v) => { setSelectedYear(v); if (v === "all") setSelectedMonth("all"); }}>
|
||||
<SelectTrigger className="w-28 h-8 text-sm">
|
||||
<SelectValue placeholder="Année" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Toute année</SelectItem>
|
||||
{Array.from({ length: 5 }, (_, i) => currentYear - i).map(y => (
|
||||
<SelectItem key={y} value={String(y)}>{y}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* Month selector — disabled when year = all */}
|
||||
<Select
|
||||
value={selectedMonth}
|
||||
onValueChange={setSelectedMonth}
|
||||
disabled={selectedYear === "all"}
|
||||
{/* Ligne 1 : Recherche */}
|
||||
<div className="flex gap-3 items-center flex-wrap mb-3">
|
||||
<div className="relative flex-1 min-w-[220px]">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-emerald-400 w-4 h-4" />
|
||||
<Input
|
||||
placeholder="Rechercher par fournisseur ou numéro..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 bg-white border-emerald-200 focus:border-emerald-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ligne 2 : Filtres période (année + mois) */}
|
||||
<div className="flex flex-wrap items-center gap-2 mb-3">
|
||||
<div className="flex items-center gap-1.5 text-sm font-medium text-emerald-600">
|
||||
<CalendarDays className="w-4 h-4" />
|
||||
Période :
|
||||
</div>
|
||||
<Select value={selectedYear} onValueChange={(v) => { setSelectedYear(v); if (v === "all") setSelectedMonth("all"); }}>
|
||||
<SelectTrigger className="w-28 h-8 text-sm bg-white border-emerald-200">
|
||||
<SelectValue placeholder="Année" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Toute année</SelectItem>
|
||||
{Array.from({ length: 5 }, (_, i) => currentYear - i).map(y => (
|
||||
<SelectItem key={y} value={String(y)}>{y}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={selectedMonth}
|
||||
onValueChange={setSelectedMonth}
|
||||
disabled={selectedYear === "all"}
|
||||
>
|
||||
<SelectTrigger className="w-36 h-8 text-sm bg-white border-emerald-200">
|
||||
<SelectValue placeholder="Mois" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Tous les mois</SelectItem>
|
||||
{[
|
||||
["01","Janvier"],["02","Février"],["03","Mars"],["04","Avril"],
|
||||
["05","Mai"],["06","Juin"],["07","Juillet"],["08","Août"],
|
||||
["09","Septembre"],["10","Octobre"],["11","Novembre"],["12","Décembre"]
|
||||
].map(([v, label]) => (
|
||||
<SelectItem key={v} value={v}>{label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{(selectedYear !== "all" || selectedMonth !== "all") && (
|
||||
<button
|
||||
onClick={() => { setSelectedYear(String(currentYear)); setSelectedMonth("all"); }}
|
||||
className="text-xs text-emerald-600 hover:text-emerald-800 underline"
|
||||
>
|
||||
<SelectTrigger className="w-36 h-8 text-sm">
|
||||
<SelectValue placeholder="Mois" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Tous les mois</SelectItem>
|
||||
{[
|
||||
["01","Janvier"],["02","Février"],["03","Mars"],["04","Avril"],
|
||||
["05","Mai"],["06","Juin"],["07","Juillet"],["08","Août"],
|
||||
["09","Septembre"],["10","Octobre"],["11","Novembre"],["12","Décembre"]
|
||||
].map(([v, label]) => (
|
||||
<SelectItem key={v} value={v}>{label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{(selectedYear !== "all" || selectedMonth !== "all") && (
|
||||
<button
|
||||
onClick={() => { setSelectedYear(String(currentYear)); setSelectedMonth("all"); }}
|
||||
className="text-xs text-slate-500 hover:text-slate-700 underline"
|
||||
>
|
||||
Réinitialiser
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
Réinitialiser
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tri */}
|
||||
<div className="flex gap-2 items-center mb-3">
|
||||
<span className="text-sm text-gray-500">Trier par :</span>
|
||||
{/* Ligne 3 : Boutons statut + tri */}
|
||||
<div className="flex gap-2 items-center flex-wrap">
|
||||
<Button
|
||||
variant={statusFilter === "all" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("all")}
|
||||
size="sm"
|
||||
className={statusFilter === "all" ? "bg-emerald-600 hover:bg-emerald-700" : "bg-white border-emerald-200 text-emerald-700 hover:bg-emerald-50"}
|
||||
>
|
||||
Tous ({statusCounts.all})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "exported" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("exported")}
|
||||
size="sm"
|
||||
className={statusFilter === "exported" ? "bg-green-600 hover:bg-green-700 text-white" : "bg-white border-green-200 text-green-700 hover:bg-green-50"}
|
||||
>
|
||||
Exportés ({statusCounts.exported})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "not_exported" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("not_exported")}
|
||||
size="sm"
|
||||
className={statusFilter === "not_exported" ? "bg-indigo-600 hover:bg-indigo-700 text-white" : "bg-white border-indigo-200 text-indigo-700 hover:bg-indigo-50"}
|
||||
>
|
||||
Non exportés ({statusCounts.not_exported})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "export_error" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("export_error")}
|
||||
size="sm"
|
||||
className={statusFilter === "export_error" ? "bg-red-600 hover:bg-red-700 text-white" : "bg-white border-red-200 text-red-700 hover:bg-red-50"}
|
||||
>
|
||||
Erreurs ({statusCounts.export_error})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "bap_validated" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("bap_validated")}
|
||||
size="sm"
|
||||
className={statusFilter === "bap_validated" ? "bg-emerald-600 hover:bg-emerald-700 text-white" : "bg-white border-emerald-200 text-emerald-700 hover:bg-emerald-50"}
|
||||
>
|
||||
✅ Validées BAP ({statusCounts.bap_validated})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "bap_pending" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("bap_pending")}
|
||||
size="sm"
|
||||
className={statusFilter === "bap_pending" ? "bg-orange-600 hover:bg-orange-700 text-white" : "bg-white border-orange-200 text-orange-700 hover:bg-orange-50"}
|
||||
>
|
||||
⏳ En attente BAP ({statusCounts.bap_pending})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "to_complete" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("to_complete")}
|
||||
size="sm"
|
||||
className={statusFilter === "to_complete" ? "bg-amber-600 hover:bg-amber-700 text-white" : "bg-white border-amber-200 text-amber-700 hover:bg-amber-50"}
|
||||
>
|
||||
✏️ À compléter ({statusCounts.to_complete})
|
||||
</Button>
|
||||
<div className="ml-auto flex gap-1 items-center">
|
||||
<span className="text-xs text-emerald-500 font-medium mr-1">Trier :</span>
|
||||
<Button
|
||||
variant={sortField === "createdAt" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleSort("createdAt")}
|
||||
className={sortField === "createdAt" ? "bg-emerald-600 hover:bg-emerald-700" : "bg-white border-emerald-200 text-emerald-700 hover:bg-emerald-50"}
|
||||
>
|
||||
Date réception <SortIcon field="createdAt" />
|
||||
Réception <SortIcon field="createdAt" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={sortField === "invoiceDate" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleSort("invoiceDate")}
|
||||
className={sortField === "invoiceDate" ? "bg-emerald-600 hover:bg-emerald-700" : "bg-white border-emerald-200 text-emerald-700 hover:bg-emerald-50"}
|
||||
>
|
||||
Date facture <SortIcon field="invoiceDate" />
|
||||
Facture <SortIcon field="invoiceDate" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* ===== FIN CARTOUCHE BAP ===== */}
|
||||
|
||||
{/* Status Filters */}
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
<Button
|
||||
variant={statusFilter === "all" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("all")}
|
||||
size="sm"
|
||||
>
|
||||
Tous ({statusCounts.all})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "exported" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("exported")}
|
||||
size="sm"
|
||||
className={statusFilter === "exported" ? "bg-green-600 hover:bg-green-700" : ""}
|
||||
>
|
||||
Exportés ({statusCounts.exported})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "not_exported" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("not_exported")}
|
||||
size="sm"
|
||||
className={statusFilter === "not_exported" ? "bg-blue-600 hover:bg-blue-700" : ""}
|
||||
>
|
||||
Non exportés ({statusCounts.not_exported})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "export_error" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("export_error")}
|
||||
size="sm"
|
||||
className={statusFilter === "export_error" ? "bg-red-600 hover:bg-red-700" : ""}
|
||||
>
|
||||
Erreurs ({statusCounts.export_error})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "bap_validated" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("bap_validated")}
|
||||
size="sm"
|
||||
className={statusFilter === "bap_validated" ? "bg-emerald-600 hover:bg-emerald-700" : ""}
|
||||
>
|
||||
✅ Validées BAP ({statusCounts.bap_validated})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "bap_pending" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("bap_pending")}
|
||||
size="sm"
|
||||
className={statusFilter === "bap_pending" ? "bg-orange-600 hover:bg-orange-700" : ""}
|
||||
>
|
||||
⏳ En attente BAP ({statusCounts.bap_pending})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "to_complete" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("to_complete")}
|
||||
size="sm"
|
||||
className={statusFilter === "to_complete" ? "bg-amber-600 hover:bg-amber-700" : "border-amber-400 text-amber-700 hover:bg-amber-50"}
|
||||
>
|
||||
✏️ À compléter ({statusCounts.to_complete})
|
||||
</Button>
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
|
||||
{/* Table */}
|
||||
{isLoading ? (
|
||||
|
||||
Reference in New Issue
Block a user