feat: Add AI Copilot Zustand store for managing and persisting product insights.

This commit is contained in:
Michael committed 2026-03-23 13:34:36 +01:00
1 parent 86df5d6f29
commit 99954fb001
2 files changed
+26 -10

No files matched your search

@@ -13,6 +13,7 @@ interface AiInsight {
interface AiCopilotState { interface AiCopilotState {
insights: Record<string, AiInsight>; insights: Record<string, AiInsight>;
setInsight: (codein: string, insight: string, isDuplicate?: boolean) => void; setInsight: (codein: string, insight: string, isDuplicate?: boolean) => void;
batchSetInsights: (entries: { codein: string; insight: string }[]) => void;
setLoading: (codein: string) => void; setLoading: (codein: string) => void;
batchSetLoading: (codeins: string[]) => void; batchSetLoading: (codeins: string[]) => void;
setError: (codein: string, error: string) => void; setError: (codein: string, error: string) => void;
@@ -76,6 +77,16 @@ export const useAiCopilotStore = create<AiCopilotState>()(
})); }));
}, },
batchSetInsights: (entries) => {
set((s) => {
const nextInsights = { ...s.insights };
entries.forEach(({ codein, insight }) => {
nextInsights[codein] = { codein, insight, status: "done" };
});
return { insights: nextInsights };
});
},
setLoading: (codein) => { setLoading: (codein) => {
set((s) => ({ set((s) => ({
insights: { insights: {
@@ -20,7 +20,7 @@ export function BulkAiAnalyzer() {
const batchSetDraftGamme = useGridStore((s) => s.batchSetDraftGamme); const batchSetDraftGamme = useGridStore((s) => s.batchSetDraftGamme);
const setDraftGamme = useGridStore((s) => s.setDraftGamme); const setDraftGamme = useGridStore((s) => s.setDraftGamme);
const { setInsight, batchSetLoading, setError } = useAiCopilotStore(); const { setInsight, batchSetInsights, batchSetLoading, setError } = useAiCopilotStore();
const prevSupplierRef = useRef(supplierCode); const prevSupplierRef = useRef(supplierCode);
useEffect(() => { useEffect(() => {
@@ -61,9 +61,10 @@ export function BulkAiAnalyzer() {
const zChanges: Record<string, GammeCode> = {}; const zChanges: Record<string, GammeCode> = {};
zeroSalesRows.forEach(r => { zChanges[r.codein] = "Z"; }); zeroSalesRows.forEach(r => { zChanges[r.codein] = "Z"; });
batchSetDraftGamme(zChanges); batchSetDraftGamme(zChanges);
zeroSalesRows.forEach(r => { batchSetInsights(zeroSalesRows.map(r => ({
setInsight(r.codein, "Aucune vente sur 12 mois — produit classé Z automatiquement."); codein: r.codein,
}); insight: "Aucune vente sur 12 mois — produit classé Z automatiquement.",
})));
} }
// Construire les payloads uniquement pour les produits avec ventes // Construire les payloads uniquement pour les produits avec ventes
@@ -129,9 +130,10 @@ export function BulkAiAnalyzer() {
const zChanges: Record<string, GammeCode> = {}; const zChanges: Record<string, GammeCode> = {};
deadStockRows.forEach(r => { zChanges[r.codein] = "Z"; }); deadStockRows.forEach(r => { zChanges[r.codein] = "Z"; });
batchSetDraftGamme(zChanges); batchSetDraftGamme(zChanges);
deadStockRows.forEach(r => { batchSetInsights(deadStockRows.map(r => ({
setInsight(r.codein, `CA < 100€ (${(r.totalCa ?? 0).toFixed(0)}€) et quantité < 30 (${r.totalQuantite} uté) — stock mort absolu, classé Z automatiquement.`); codein: r.codein,
}); insight: `CA < 100€ (${(r.totalCa ?? 0).toFixed(0)}€) et quantité < 30 (${r.totalQuantite} uté) — stock mort absolu, classé Z automatiquement.`,
})));
} }
const deadStockCodes = new Set(deadStockRows.map(r => r.codein)); const deadStockCodes = new Set(deadStockRows.map(r => r.codein));
@@ -145,12 +147,15 @@ export function BulkAiAnalyzer() {
const aChanges: Record<string, GammeCode> = {}; const aChanges: Record<string, GammeCode> = {};
top20Rows.forEach(r => { aChanges[r.codein] = "A"; }); top20Rows.forEach(r => { aChanges[r.codein] = "A"; });
batchSetDraftGamme(aChanges); batchSetDraftGamme(aChanges);
top20Rows.forEach(r => { batchSetInsights(top20Rows.map(r => {
const rkCa = r.rankingCa != null ? `${r.rankingCa}e CA` : ""; const rkCa = r.rankingCa != null ? `${r.rankingCa}e CA` : "";
const rkQte = r.rankingQte != null ? `${r.rankingQte}e Qté` : ""; const rkQte = r.rankingQte != null ? `${r.rankingQte}e Qté` : "";
const total = r.totalRankedProducts ? ` / ${r.totalRankedProducts.toLocaleString("fr-FR")} produits` : ""; const total = r.totalRankedProducts ? ` / ${r.totalRankedProducts.toLocaleString("fr-FR")} produits` : "";
setInsight(r.codein, `Top 20 réseau (${[rkCa, rkQte].filter(Boolean).join(", ")}${total}) — classé A automatiquement.`); return {
}); codein: r.codein,
insight: `Top 20 réseau (${[rkCa, rkQte].filter(Boolean).join(", ")}${total}) — classé A automatiquement.`,
};
}));
} }
const top20Codes = new Set(top20Rows.map(r => r.codein)); const top20Codes = new Set(top20Rows.map(r => r.codein));