mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
feat(grid): make gamme filters dynamic based on available data and support Aucune state
This commit is contained in:
1 parent
86c6946c3e
commit
d3b349cc31
3 files changed
+44
-18
No files matched your search
@@ -5,32 +5,35 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
interface GammeSelectProps {
|
||||
value: GammeCode | null;
|
||||
isDraft: boolean;
|
||||
isDraft?: boolean;
|
||||
onChange: (gamme: GammeCode) => void;
|
||||
}
|
||||
|
||||
const GAMME_STYLES: Record<GammeCode, string> = {
|
||||
const GAMME_STYLES: Record<string, string> = {
|
||||
A: "border-[1px] border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/20 dark:text-emerald-400",
|
||||
B: "border-[1px] border-blue-500/30 bg-blue-500/10 text-blue-700 dark:border-blue-500/30 dark:bg-blue-500/20 dark:text-blue-400",
|
||||
C: "border-[1px] border-amber-500/30 bg-amber-500/10 text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/20 dark:text-amber-400",
|
||||
Z: "border-[1px] border-rose-500/30 bg-rose-500/10 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/20 dark:text-rose-400",
|
||||
Aucune: "border-[1px] border-slate-500/30 bg-slate-500/10 text-slate-700 dark:border-slate-500/30 dark:bg-slate-500/20 dark:text-slate-400",
|
||||
};
|
||||
|
||||
export function GammeSelect({ value, isDraft, onChange }: GammeSelectProps) {
|
||||
const displayValue = (!value || value.trim() === "") ? "Aucune" : value;
|
||||
|
||||
return (
|
||||
<select
|
||||
value={value ?? ""}
|
||||
value={displayValue}
|
||||
onChange={(e) => onChange(e.target.value as GammeCode)}
|
||||
className={cn(
|
||||
"w-full text-xs font-bold rounded-lg py-1.5 px-2 outline-none cursor-pointer shadow-sm transition-all text-center",
|
||||
"focus:ring-2 focus:ring-brand-500/20",
|
||||
value
|
||||
? GAMME_STYLES[value]
|
||||
: "border-[1px] border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-500",
|
||||
GAMME_STYLES[displayValue] || "border-[1px] border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-500",
|
||||
isDraft && "shadow-[0_0_8px_rgba(251,191,36,0.3)] border-amber-400/50"
|
||||
)}
|
||||
>
|
||||
<option value="" disabled>—</option>
|
||||
<option value="Aucune" disabled={displayValue !== "Aucune" && displayValue !== ""}>
|
||||
{displayValue === "Aucune" || displayValue === "" ? "— (Aucune)" : "—"}
|
||||
</option>
|
||||
<option value="A">A</option>
|
||||
<option value="B">B</option>
|
||||
<option value="C">C</option>
|
||||
|
||||
@@ -30,19 +30,41 @@ export function GridFilterBar({ fournisseurs, magasins, nomenclature }: GridFilt
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
// Compute dynamic counts per Gamme
|
||||
const gammeCounts = React.useMemo(() => {
|
||||
const counts: Record<string, number> = { A: 0, B: 0, C: 0, Z: 0, Total: 0 };
|
||||
// Compute dynamic counts and present Gammes
|
||||
const { gammeCounts, activeGammes } = React.useMemo(() => {
|
||||
const counts: Record<string, number> = { Total: 0 };
|
||||
const gammesPresence = new Set<string>();
|
||||
|
||||
rows.forEach(r => {
|
||||
const effectiveGamme = draftChanges[r.codein] ?? r.codeGamme;
|
||||
if (effectiveGamme && counts[effectiveGamme] !== undefined) {
|
||||
counts[effectiveGamme]++;
|
||||
let effectiveGamme = draftChanges[r.codein] ?? r.codeGamme;
|
||||
|
||||
// Handle null, empty, or undefined gammes as "Aucune"
|
||||
if (!effectiveGamme || effectiveGamme.trim() === "") {
|
||||
effectiveGamme = "Aucune";
|
||||
}
|
||||
|
||||
gammesPresence.add(effectiveGamme);
|
||||
counts[effectiveGamme] = (counts[effectiveGamme] || 0) + 1;
|
||||
|
||||
if (effectiveGamme !== "Z") {
|
||||
counts.Total++; // Active references (non-Z)
|
||||
}
|
||||
});
|
||||
return counts;
|
||||
|
||||
// Always ensure A, B, C, Z are available IF they have count > 0, plus any other discovered gammes
|
||||
const sortedGammes = Array.from(gammesPresence).sort((a, b) => {
|
||||
if (a === "Aucune") return 1; // Put "Aucune" at the end
|
||||
if (b === "Aucune") return -1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
// Build the dynamic filter list
|
||||
const filtersList: { label: string; value: string | null }[] = [
|
||||
{ label: "Tous", value: null },
|
||||
...sortedGammes.map(g => ({ label: g, value: g }))
|
||||
];
|
||||
|
||||
return { gammeCounts: counts, activeGammes: filtersList };
|
||||
}, [rows, draftChanges]);
|
||||
|
||||
const handleSupplierSelect = (code: string) => {
|
||||
@@ -104,8 +126,8 @@ export function GridFilterBar({ fournisseurs, magasins, nomenclature }: GridFilt
|
||||
className="flex items-center gap-1 p-0.5 rounded-lg"
|
||||
style={{ background: "var(--bg-elevated)", border: "1px solid var(--border)" }}
|
||||
>
|
||||
{GAMME_FILTERS.map(({ label, value }) => {
|
||||
const count = value === null ? rows.length : gammeCounts[value];
|
||||
{activeGammes.map(({ label, value }) => {
|
||||
const count = value === null ? rows.length : gammeCounts[value] || 0;
|
||||
const isActive = filters.codeGamme === value;
|
||||
|
||||
// Semantic colors for the badge based on Gamme
|
||||
@@ -116,6 +138,7 @@ export function GridFilterBar({ fournisseurs, magasins, nomenclature }: GridFilt
|
||||
case "B": return { background: "rgba(59, 130, 246, 0.15)", color: "rgb(37, 99, 235)" }; // Blue
|
||||
case "C": return { background: "rgba(245, 158, 11, 0.15)", color: "rgb(217, 119, 6)" }; // Amber
|
||||
case "Z": return { background: "rgba(244, 63, 94, 0.15)", color: "rgb(225, 29, 72)" }; // Rose
|
||||
case "Aucune": return { background: "rgba(100, 116, 139, 0.15)", color: "rgb(71, 85, 105)" }; // Slate
|
||||
default: return { background: "rgba(0,0,0,0.1)", color: "var(--text-primary)" };
|
||||
}
|
||||
};
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ export interface VenteProduit {
|
||||
margeMvt: number | null;
|
||||
}
|
||||
|
||||
/** A/B/C/Z status codes for product categories */
|
||||
export type GammeCode = "A" | "B" | "C" | "Z";
|
||||
/** A/B/C/Z status codes for product categories, but can be any string from DB (like 'Aucune') */
|
||||
export type GammeCode = string;
|
||||
|
||||
/** A product row with its 12-month sales history and computed KPIs */
|
||||
export interface ProductRow {
|
||||
|
||||
Reference in new issue
Block a user