mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-19 05:06:56 +02:00
fix(ui): add hydration guards to settings and grid pages to fix error #418
This commit is contained in:
1 parent
69e7db4a9d
commit
643b26dcf3
2 files changed
+35
-18
No files matched your search
@@ -59,6 +59,8 @@ export default function SettingsPage() {
|
|||||||
const [selectedModel, setSelectedModel] = useState("google/gemini-flash-1.5");
|
const [selectedModel, setSelectedModel] = useState("google/gemini-flash-1.5");
|
||||||
const [modelsStatus, setModelsStatus] = useState<"idle" | "loading" | "ok" | "error">("idle");
|
const [modelsStatus, setModelsStatus] = useState<"idle" | "loading" | "ok" | "error">("idle");
|
||||||
|
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
host, setHost,
|
host, setHost,
|
||||||
port, setPort,
|
port, setPort,
|
||||||
@@ -70,10 +72,27 @@ export default function SettingsPage() {
|
|||||||
} = useDbSettingsStore();
|
} = useDbSettingsStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Optionnel : On pourrait charger la config serveur au montage si on veut écraser le localStorage
|
setIsMounted(true);
|
||||||
// Pour l'instant on laisse le localStorage faire son travail, mais on offre une fonction de reset
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fetchModels = useCallback(async (key: string) => {
|
||||||
|
if (!key.trim()) return;
|
||||||
|
setModelsStatus("loading");
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/openrouter/models", { headers: { "x-openrouter-key": key } });
|
||||||
|
if (!res.ok) throw new Error();
|
||||||
|
const data = await res.json();
|
||||||
|
setModels(data.models ?? []);
|
||||||
|
setModelsStatus("ok");
|
||||||
|
} catch {
|
||||||
|
setModelsStatus("error");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!isMounted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const reloadFromServer = async () => {
|
const reloadFromServer = async () => {
|
||||||
const config = await getSavedDatabaseConfig();
|
const config = await getSavedDatabaseConfig();
|
||||||
if (config && config.url) {
|
if (config && config.url) {
|
||||||
@@ -92,20 +111,6 @@ export default function SettingsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchModels = useCallback(async (key: string) => {
|
|
||||||
if (!key.trim()) return;
|
|
||||||
setModelsStatus("loading");
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/openrouter/models", { headers: { "x-openrouter-key": key } });
|
|
||||||
if (!res.ok) throw new Error();
|
|
||||||
const data = await res.json();
|
|
||||||
setModels(data.models ?? []);
|
|
||||||
setModelsStatus("ok");
|
|
||||||
} catch {
|
|
||||||
setModelsStatus("error");
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const testDb = async () => {
|
const testDb = async () => {
|
||||||
setDbStatus("testing");
|
setDbStatus("testing");
|
||||||
setDbError(null);
|
setDbError(null);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ interface GridClientProps {
|
|||||||
magasin: string;
|
magasin: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fournisseurs, magasins, magasin }: GridClientProps) {
|
export function GridClient({ initialRows, nomFournisseur, fournisseurs, magasins, magasin }: GridClientProps) {
|
||||||
const setRows = useGridStore((s) => s.setRows);
|
const setRows = useGridStore((s) => s.setRows);
|
||||||
const seuilAxeFort = useScoreSettingsStore((s) => s.seuilAxeFort);
|
const seuilAxeFort = useScoreSettingsStore((s) => s.seuilAxeFort);
|
||||||
const bonusParAxe = useScoreSettingsStore((s) => s.bonusParAxe);
|
const bonusParAxe = useScoreSettingsStore((s) => s.bonusParAxe);
|
||||||
@@ -32,12 +32,20 @@ export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fourn
|
|||||||
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||||
const { save, hasDrafts, count } = useSaveDrafts(magasin);
|
const { save, hasDrafts, count } = useSaveDrafts(magasin);
|
||||||
|
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMounted) return;
|
||||||
// Deep clone to avoid mutating the React prop directly across re-renders
|
// Deep clone to avoid mutating the React prop directly across re-renders
|
||||||
const rowsCopy = JSON.parse(JSON.stringify(initialRows));
|
const rowsCopy = JSON.parse(JSON.stringify(initialRows));
|
||||||
const scoredRows = computeProductScores(rowsCopy, { seuilAxeFort, bonusParAxe });
|
const scoredRows = computeProductScores(rowsCopy, { seuilAxeFort, bonusParAxe });
|
||||||
setRows(scoredRows);
|
setRows(scoredRows);
|
||||||
}, [initialRows, setRows, seuilAxeFort, bonusParAxe]);
|
}, [initialRows, setRows, seuilAxeFort, bonusParAxe, isMounted]);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
@@ -47,6 +55,10 @@ export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fourn
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!isMounted) {
|
||||||
|
return <div className="p-8 text-center animate-pulse text-muted italic">Initialisation de la grille...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
const activeStoreNom = magasins.find(m => m.code === magasin)?.nom || "National (Total)";
|
const activeStoreNom = magasins.find(m => m.code === magasin)?.nom || "National (Total)";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in new issue
Block a user