feat: add settings page for database, OpenRouter, score, theme, and display density configurations.

This commit is contained in:
Michael committed 2026-02-22 14:16:49 +01:00
1 parent 359d196c4d
commit cfd079fbf5
7 files changed
+41 -28

No files matched your search

-2
View File
@@ -9,8 +9,6 @@ import { useDbSettingsStore } from "@/features/settings/store/use-db-settings-st
import { testDatabaseConnection, saveDatabaseSettings, getSavedDatabaseConfig } from "@/features/settings/actions"; import { testDatabaseConnection, saveDatabaseSettings, getSavedDatabaseConfig } from "@/features/settings/actions";
import { useEffect } from "react"; import { useEffect } from "react";
import { UserManagement } from "@/features/admin/components/user-management"; import { UserManagement } from "@/features/admin/components/user-management";
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
interface OpenRouterModel { id: string; name: string; free: boolean; } interface OpenRouterModel { id: string; name: string; free: boolean; }
@@ -12,9 +12,9 @@ En analysant les données de ventes fournies, génère une recommandation de gam
Critères de pondération et Règles Métier Strictes : Critères de pondération et Règles Métier Strictes :
1. Normalisation Totale (Base 2 magasins) : TOUTES les statistiques fournies sont PONDÉRÉES par 2 si le produit n'est au catalogue que de 1 magasin. 1. Normalisation Totale (Base 2 magasins) : TOUTES les statistiques fournies sont PONDÉRÉES par 2 si le produit n'est au catalogue que de 1 magasin.
2. Segmentation par Rayon (Univers) : Privilégie la comparaison "Intra-Rayon". Un produit doit être performant par rapport aux standards de son propre Rayon (Niveau 2). 2. Segmentation par Rayon (Univers) : Privilégie la comparaison "Intra-Rayon". Un produit doit être performant par rapport aux standards de son propre Rayon (Niveau 2).
3. Seuil "Permanent" (A) : Pour être A, un produit doit avoir un volume PONDÉRÉ significatif (ex: > 30-50 unités/an). 3. Score de Performance Global (0-10) : Un score > 7 est un indicateur fort pour "Permanent" (A). Un score < 3 est un indicateur fort pour "Sortie" (Z).
4. Seuil "Sortie" (Z) : Un produit avec moins de 10 unités pondérées par an et un CA faible est un candidat à la sortie (Z). 4. Régularité des Ventes (0-12 mois) : Un produit avec une vente régulière (> 8 mois/an) est un candidat idéal pour "Permanent" (A). Une régularité faible (< 3 mois) couplée à un volume total faible indique une "Sortie" (Z).
5. Équilibre : Un produit "A" doit justifier sa place par son flux OU sa rentabilité brute. 5. Équilibre : Un produit "A" doit justifier sa place par son flux, sa rentabilité brute OU son score global.
Ta réponse doit être courte, directe et sans complaisance. Ta réponse doit être courte, directe et sans complaisance.
Format impératif : "[Recommandation]: [Justification courte]"`; Format impératif : "[Recommandation]: [Justification courte]"`;
@@ -38,8 +38,10 @@ Benchmarks Rayon ("${p.libelleNiveau2}") :
Rayon : ${p.libelleNiveau2 || "Non classé"} Rayon : ${p.libelleNiveau2 || "Non classé"}
Gamme actuelle : ${p.codeGamme ?? "Non définie"} Gamme actuelle : ${p.codeGamme ?? "Non définie"}
${volumeInfo}${benchmarks} ${volumeInfo}${benchmarks}
Indicateurs Clés : Indicateurs de Performance :
- Marge : ${p.tauxMarge.toFixed(1)}% - Score Global App : ${p.score.toFixed(1)}/10
- Régularité des ventes : ${p.regularityScore}/12 mois
- Marge brute : ${p.tauxMarge.toFixed(1)}%
- Historique mensuel (Pondéré) : ${monthlySummary} - Historique mensuel (Pondéré) : ${monthlySummary}
Quelle est ta recommandation (A, C ou Z) et pourquoi ?`; Quelle est ta recommandation (A, C ou Z) et pourquoi ?`;
@@ -15,6 +15,8 @@ export interface ProductAnalysisInput {
storeCount: number; storeCount: number;
sales12m: Record<string, number>; sales12m: Record<string, number>;
codeGamme: string | null; codeGamme: string | null;
score: number;
regularityScore: number;
} }
export interface AnalysisResult { export interface AnalysisResult {
@@ -80,6 +80,8 @@ export function BulkAiAnalyzer() {
Object.entries(r.sales12m || {}).map(([month, val]) => [month, val * weight]) Object.entries(r.sales12m || {}).map(([month, val]) => [month, val * weight])
), ),
codeGamme: r.codeGamme || null, codeGamme: r.codeGamme || null,
score: r.score || 0,
regularityScore: Object.values(r.sales12m || {}).filter(v => v > 0).length,
}; };
}); });
+24
View File
@@ -0,0 +1,24 @@
import type { NextAuthConfig } from "next-auth";
export const authConfig = {
pages: {
signIn: "/login",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = (user as any).role;
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
(session.user as any).role = token.role as string;
(session.user as any).id = token.id as string;
}
return session;
},
},
providers: [], // Les providers seront ajoutés dans auth.ts
} satisfies NextAuthConfig;
+2 -20
View File
@@ -5,8 +5,10 @@ import { users } from "@/db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { verifyPassword } from "@/features/auth/logic/auth-logic"; import { verifyPassword } from "@/features/auth/logic/auth-logic";
import { ensureAdminExists } from "@/features/auth/logic/seed-admin"; import { ensureAdminExists } from "@/features/auth/logic/seed-admin";
import { authConfig } from "./auth.config";
export const { handlers, auth, signIn, signOut } = NextAuth({ export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [ providers: [
Credentials({ Credentials({
name: "Credentials", name: "Credentials",
@@ -15,7 +17,6 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
password: { label: "Mot de passe", type: "password" } password: { label: "Mot de passe", type: "password" }
}, },
async authorize(credentials) { async authorize(credentials) {
// S'assure que le compte admin par défaut existe
await ensureAdminExists(); await ensureAdminExists();
if (!credentials?.username || !credentials?.password) return null; if (!credentials?.username || !credentials?.password) return null;
@@ -42,23 +43,4 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
} }
}) })
], ],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = (user as any).role;
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
(session.user as any).role = token.role as string;
(session.user as any).id = token.id as string;
}
return session;
},
},
pages: {
signIn: "/login",
}
}); });
+4 -1
View File
@@ -1,6 +1,9 @@
import { auth } from "@/lib/auth"; import NextAuth from "next-auth";
import { authConfig } from "@/lib/auth.config";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
const { auth } = NextAuth(authConfig);
export default auth((req) => { export default auth((req) => {
const isLoggedIn = !!req.auth; const isLoggedIn = !!req.auth;
const { nextUrl } = req; const { nextUrl } = req;