feat: Add FF Nancy API client and status endpoint, implement grid product fetching and draft change saving, and introduce AI Copilot core logic.

This commit is contained in:
Michael committed 2026-03-18 14:31:29 +01:00
1 parent 22aa6f101a
commit 1035489db1
13 files changed
+706 -265

No files matched your search

+1 -1
View File
@@ -26,7 +26,7 @@ export default async function GridPage({ searchParams }: GridPageProps) {
const [fournisseurs, magasins, nomenclature] = await Promise.all([
getFournisseurs(),
getMagasins(),
getAvailableNomenclature(codeFournisseur, magasin)
getAvailableNomenclature()
]);
// 2. If no supplier selected, show compact selection UI
+75 -1
View File
@@ -36,6 +36,77 @@ function Field({ label, hint, children }: { label: string; hint?: string; childr
);
}
interface FfSyncTable { nom: string; derniereSync: string; nbLignes?: number; }
interface FfSyncStatus { lastSync: string; tables: FfSyncTable[]; }
function FfApiStatusSection() {
const [status, setStatus] = useState<FfSyncStatus | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchStatus = useCallback(async () => {
setLoading(true);
setError(null);
try {
const res = await fetch("/api/ff-status");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
setStatus(await res.json());
} catch (e) {
setError(e instanceof Error ? e.message : "Erreur inconnue");
} finally {
setLoading(false);
}
}, []);
useEffect(() => { fetchStatus(); }, [fetchStatus]);
return (
<Section title="API FF Nancy" subtitle="Source des données produits — synchronisation nuit depuis SQL Server (données J-1)">
<div className="flex items-center gap-3">
<button onClick={fetchStatus} disabled={loading} className="apple-btn-secondary h-9 px-4">
{loading ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <RefreshCw className="w-3.5 h-3.5" />}
Vérifier le statut
</button>
{error && <span className="text-[12px] text-red-400">{error}</span>}
</div>
{status && (
<div className="space-y-2 mt-2">
<p className="text-[12px] text-[var(--text-secondary)]">
Dernière sync globale : <span className="font-mono font-medium">{new Date(status.lastSync).toLocaleString("fr-FR")}</span>
</p>
{status.tables?.length > 0 && (
<div className="rounded-lg overflow-hidden border border-[var(--border-subtle)]">
<table className="w-full text-[11px]">
<thead>
<tr className="bg-[var(--surface-tertiary)]">
<th className="text-left px-3 py-2 font-medium text-[var(--text-secondary)]">Table</th>
<th className="text-right px-3 py-2 font-medium text-[var(--text-secondary)]">Sync</th>
<th className="text-right px-3 py-2 font-medium text-[var(--text-secondary)]">Lignes</th>
</tr>
</thead>
<tbody>
{status.tables.map((t) => (
<tr key={t.nom} className="border-t border-[var(--border-subtle)]">
<td className="px-3 py-2 font-mono text-[var(--text-primary)]">{t.nom}</td>
<td className="px-3 py-2 text-right text-[var(--text-secondary)]">
{new Date(t.derniereSync).toLocaleString("fr-FR")}
</td>
<td className="px-3 py-2 text-right text-[var(--text-muted)]">
{t.nbLignes?.toLocaleString("fr-FR") ?? "—"}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
)}
</Section>
);
}
export default function SettingsPage() {
const { resolvedTheme, setTheme } = useTheme();
const isDark = resolvedTheme === "dark";
@@ -155,7 +226,7 @@ export default function SettingsPage() {
</div>
{/* PostgreSQL */}
<Section title="PostgreSQL" subtitle="Connexion à votre instance Docker ou distante">
<Section title="PostgreSQL — Auth & Historique" subtitle="Connexion pour les comptes utilisateurs, snapshots et contexte IA. Les données produits proviennent de l'API FF Nancy.">
<Field label="Hôte / Nom du container" hint="Ex: localhost, 192.168.1.10, ou nom du service Docker (ex: postgres, db)">
<input
type="text"
@@ -243,6 +314,9 @@ export default function SettingsPage() {
</div>
</Section>
{/* API FF Nancy */}
<FfApiStatusSection />
{/* OpenRouter */}
<Section title="IA Copilot — OpenRouter" subtitle="Clé API pour les analyses de gammes par intelligence artificielle">
<Field label="Clé API" hint="Disponible sur openrouter.ai/keys — commence par sk-or-...">
+10
View File
@@ -0,0 +1,10 @@
import { NextResponse } from "next/server";
import { getSyncStatus } from "@/lib/api-ff-client";
export async function GET() {
const status = await getSyncStatus();
if (!status) {
return NextResponse.json({ error: "API FF Nancy non disponible" }, { status: 503 });
}
return NextResponse.json(status);
}