mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-24 15:38:09 +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 {
|
interface GammeSelectProps {
|
||||||
value: GammeCode | null;
|
value: GammeCode | null;
|
||||||
isDraft: boolean;
|
isDraft?: boolean;
|
||||||
onChange: (gamme: GammeCode) => void;
|
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",
|
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",
|
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",
|
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",
|
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) {
|
export function GammeSelect({ value, isDraft, onChange }: GammeSelectProps) {
|
||||||
|
const displayValue = (!value || value.trim() === "") ? "Aucune" : value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
value={value ?? ""}
|
value={displayValue}
|
||||||
onChange={(e) => onChange(e.target.value as GammeCode)}
|
onChange={(e) => onChange(e.target.value as GammeCode)}
|
||||||
className={cn(
|
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",
|
"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",
|
"focus:ring-2 focus:ring-brand-500/20",
|
||||||
value
|
GAMME_STYLES[displayValue] || "border-[1px] border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-500",
|
||||||
? GAMME_STYLES[value]
|
|
||||||
: "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"
|
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="A">A</option>
|
||||||
<option value="B">B</option>
|
<option value="B">B</option>
|
||||||
<option value="C">C</option>
|
<option value="C">C</option>
|
||||||
|
|||||||
@@ -30,19 +30,41 @@ export function GridFilterBar({ fournisseurs, magasins, nomenclature }: GridFilt
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Compute dynamic counts per Gamme
|
// Compute dynamic counts and present Gammes
|
||||||
const gammeCounts = React.useMemo(() => {
|
const { gammeCounts, activeGammes } = React.useMemo(() => {
|
||||||
const counts: Record<string, number> = { A: 0, B: 0, C: 0, Z: 0, Total: 0 };
|
const counts: Record<string, number> = { Total: 0 };
|
||||||
|
const gammesPresence = new Set<string>();
|
||||||
|
|
||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
const effectiveGamme = draftChanges[r.codein] ?? r.codeGamme;
|
let effectiveGamme = draftChanges[r.codein] ?? r.codeGamme;
|
||||||
if (effectiveGamme && counts[effectiveGamme] !== undefined) {
|
|
||||||
counts[effectiveGamme]++;
|
// 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") {
|
if (effectiveGamme !== "Z") {
|
||||||
counts.Total++; // Active references (non-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]);
|
}, [rows, draftChanges]);
|
||||||
|
|
||||||
const handleSupplierSelect = (code: string) => {
|
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"
|
className="flex items-center gap-1 p-0.5 rounded-lg"
|
||||||
style={{ background: "var(--bg-elevated)", border: "1px solid var(--border)" }}
|
style={{ background: "var(--bg-elevated)", border: "1px solid var(--border)" }}
|
||||||
>
|
>
|
||||||
{GAMME_FILTERS.map(({ label, value }) => {
|
{activeGammes.map(({ label, value }) => {
|
||||||
const count = value === null ? rows.length : gammeCounts[value];
|
const count = value === null ? rows.length : gammeCounts[value] || 0;
|
||||||
const isActive = filters.codeGamme === value;
|
const isActive = filters.codeGamme === value;
|
||||||
|
|
||||||
// Semantic colors for the badge based on Gamme
|
// 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 "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 "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 "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)" };
|
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;
|
margeMvt: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A/B/C/Z status codes for product categories */
|
/** A/B/C/Z status codes for product categories, but can be any string from DB (like 'Aucune') */
|
||||||
export type GammeCode = "A" | "B" | "C" | "Z";
|
export type GammeCode = string;
|
||||||
|
|
||||||
/** A product row with its 12-month sales history and computed KPIs */
|
/** A product row with its 12-month sales history and computed KPIs */
|
||||||
export interface ProductRow {
|
export interface ProductRow {
|
||||||
|
|||||||
Reference in new issue
Block a user