205 lines
8.0 KiB
TypeScript
205 lines
8.0 KiB
TypeScript
import { useState } from "react";
|
|
import { useStore } from "../store";
|
|
|
|
export default function SettingsPanel({ onClose }: { onClose: () => void }) {
|
|
const {
|
|
profiles, activeProfileId, setActiveProfile, createProfile, deleteProfile,
|
|
syncGuides, syncing, syncProgress,
|
|
} = useStore();
|
|
|
|
const [newName, setNewName] = useState("");
|
|
const [profileError, setProfileError] = useState("");
|
|
const [syncErrors, setSyncErrors] = useState<string[]>([]);
|
|
const [syncDone, setSyncDone] = useState(false);
|
|
|
|
async function handleCreate() {
|
|
const name = newName.trim();
|
|
if (!name) return;
|
|
if (profiles.find(p => p.name === name)) {
|
|
setProfileError("Un profil avec ce nom existe déjà.");
|
|
return;
|
|
}
|
|
await createProfile(name);
|
|
setNewName("");
|
|
setProfileError("");
|
|
}
|
|
|
|
async function handleDelete(id: string) {
|
|
if (profiles.length <= 1) {
|
|
setProfileError("Vous ne pouvez pas supprimer le dernier profil.");
|
|
return;
|
|
}
|
|
await deleteProfile(id);
|
|
}
|
|
|
|
async function handleSync() {
|
|
setSyncErrors([]);
|
|
setSyncDone(false);
|
|
const result = await syncGuides();
|
|
setSyncErrors(result.errors);
|
|
setSyncDone(true);
|
|
}
|
|
|
|
const { current = 0, total = 0, label = "" } = syncProgress ?? {};
|
|
const syncPct = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
|
|
return (
|
|
<div style={{
|
|
position: "fixed", inset: "40px 0 0 0",
|
|
background: "#0d1117", zIndex: 50,
|
|
display: "flex", flexDirection: "column", overflow: "hidden",
|
|
borderTop: "1px solid #2d3748",
|
|
}}>
|
|
{/* Header */}
|
|
<div style={{
|
|
display: "flex", alignItems: "center", justifyContent: "space-between",
|
|
padding: "14px 20px", borderBottom: "1px solid #2d3748", flexShrink: 0,
|
|
}}>
|
|
<span style={{ fontSize: "14px", fontWeight: 700, color: "#f0c040" }}>Paramètres</span>
|
|
<button
|
|
onClick={onClose}
|
|
style={{ background: "none", border: "none", color: "#94a3b8", cursor: "pointer", fontSize: "16px", lineHeight: 1 }}
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
|
|
{/* Scrollable content */}
|
|
<div style={{ flex: 1, overflowY: "auto", padding: "20px", display: "flex", flexDirection: "column", gap: "28px", scrollbarWidth: "none" }}>
|
|
|
|
{/* ── Profils ── */}
|
|
<section>
|
|
<SectionTitle>Profils</SectionTitle>
|
|
|
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px", marginBottom: "12px" }}>
|
|
{profiles.map(profile => {
|
|
const isActive = profile.id === activeProfileId;
|
|
return (
|
|
<div key={profile.id} style={{
|
|
display: "flex", alignItems: "center", gap: "8px",
|
|
background: isActive ? "rgba(240,192,64,0.07)" : "#161b22",
|
|
border: `1px solid ${isActive ? "rgba(240,192,64,0.35)" : "#2d3748"}`,
|
|
borderRadius: "8px", padding: "9px 12px",
|
|
}}>
|
|
<button
|
|
onClick={() => { setActiveProfile(profile.id); setProfileError(""); }}
|
|
style={{ flex: 1, background: "none", border: "none", textAlign: "left", cursor: "pointer" }}
|
|
>
|
|
<div style={{ fontSize: "13px", fontWeight: 600, color: isActive ? "#f0c040" : "#e2e8f0" }}>
|
|
{isActive && "✓ "}{profile.name}
|
|
</div>
|
|
<div style={{ fontSize: "10px", color: "#4a5568", marginTop: "2px" }}>
|
|
Créé le {new Date(profile.created_at).toLocaleDateString("fr-FR")}
|
|
</div>
|
|
</button>
|
|
{profiles.length > 1 && (
|
|
<button
|
|
onClick={() => handleDelete(profile.id)}
|
|
title="Supprimer ce profil"
|
|
style={{ background: "none", border: "none", color: "#f87171", cursor: "pointer", padding: "4px", borderRadius: "4px", fontSize: "12px" }}
|
|
>
|
|
🗑
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<div style={{ display: "flex", gap: "8px" }}>
|
|
<input
|
|
value={newName}
|
|
onChange={e => { setNewName(e.target.value); setProfileError(""); }}
|
|
onKeyDown={e => e.key === "Enter" && handleCreate()}
|
|
placeholder="Nouveau profil…"
|
|
style={{
|
|
flex: 1, background: "#161b22", border: "1px solid #2d3748",
|
|
borderRadius: "6px", padding: "7px 10px", color: "#e2e8f0",
|
|
fontSize: "12px", outline: "none",
|
|
}}
|
|
onFocus={e => (e.target.style.borderColor = "#f0c040")}
|
|
onBlur={e => (e.target.style.borderColor = "#2d3748")}
|
|
/>
|
|
<button
|
|
onClick={handleCreate}
|
|
disabled={!newName.trim()}
|
|
style={{
|
|
background: "#f0c040", color: "#0d1117", border: "none",
|
|
borderRadius: "6px", padding: "7px 14px", fontWeight: 700,
|
|
fontSize: "12px", cursor: newName.trim() ? "pointer" : "default",
|
|
opacity: newName.trim() ? 1 : 0.4, flexShrink: 0,
|
|
}}
|
|
>
|
|
Créer
|
|
</button>
|
|
</div>
|
|
{profileError && <p style={{ fontSize: "11px", color: "#f87171", marginTop: "6px" }}>{profileError}</p>}
|
|
</section>
|
|
|
|
{/* ── Synchronisation ── */}
|
|
<section>
|
|
<SectionTitle>Synchronisation</SectionTitle>
|
|
|
|
<p style={{ fontSize: "12px", color: "#4a5568", marginBottom: "12px", lineHeight: 1.5 }}>
|
|
Met à jour tous les guides depuis Google Sheets.
|
|
</p>
|
|
|
|
<button
|
|
onClick={handleSync}
|
|
disabled={syncing}
|
|
style={{
|
|
width: "100%", padding: "9px", borderRadius: "7px",
|
|
background: syncing ? "rgba(74,158,255,0.08)" : "rgba(74,158,255,0.12)",
|
|
border: "1px solid rgba(74,158,255,0.3)",
|
|
color: syncing ? "#4a5568" : "#4a9eff",
|
|
fontSize: "13px", fontWeight: 600, cursor: syncing ? "default" : "pointer",
|
|
display: "flex", alignItems: "center", justifyContent: "center", gap: "8px",
|
|
transition: "all 0.15s",
|
|
}}
|
|
>
|
|
<span style={{ display: "inline-block", animation: syncing ? "spin 1s linear infinite" : "none" }}>↻</span>
|
|
{syncing ? "Synchronisation…" : "Synchroniser maintenant"}
|
|
</button>
|
|
|
|
{syncing && syncProgress && (
|
|
<div style={{ marginTop: "12px" }}>
|
|
<div style={{ display: "flex", justifyContent: "space-between", fontSize: "11px", color: "#94a3b8", marginBottom: "6px" }}>
|
|
<span style={{ color: "#f0c040" }}>{label}</span>
|
|
<span>{current}/{total} — {syncPct}%</span>
|
|
</div>
|
|
<div style={{ height: "3px", background: "#2d3748", borderRadius: "2px", overflow: "hidden" }}>
|
|
<div style={{
|
|
height: "100%", width: `${syncPct}%`,
|
|
background: "linear-gradient(90deg, #4a9eff, #f0c040)",
|
|
transition: "width 0.3s ease", borderRadius: "2px",
|
|
}} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{syncDone && !syncing && (
|
|
<div style={{ marginTop: "10px", fontSize: "12px", color: syncErrors.length === 0 ? "#4ade80" : "#f87171" }}>
|
|
{syncErrors.length === 0
|
|
? "✓ Synchronisation terminée."
|
|
: `⚠ ${syncErrors.length} erreur(s) :\n${syncErrors.join("\n")}`}
|
|
</div>
|
|
)}
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SectionTitle({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div style={{
|
|
fontSize: "10px", fontWeight: 700, color: "#4a5568",
|
|
textTransform: "uppercase", letterSpacing: "0.1em",
|
|
marginBottom: "10px", paddingBottom: "6px",
|
|
borderBottom: "1px solid #2d3748",
|
|
}}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|