import { useState, useEffect, useRef } from "react"; import { RED, GREEN } from "../constants/index.js"; const TEAL = "linear-gradient(135deg, #7c3aed, #a855f7)"; const TEAL_COLOR = "#a855f7"; const TEAL_BG = "rgba(124,58,237,0.15)"; const TEAL_BORDER = "rgba(124,58,237,0.35)"; import { useApp } from "../context/AppContext.jsx"; const getToken = () => localStorage.getItem("vf_token"); const authHdrs = () => ({ "Content-Type": "application/json", Authorization: `Bearer ${getToken()}` }); async function apiFetch(path, options = {}) { const res = await fetch(`/api${path}`, { ...options, headers: { ...authHdrs(), ...(options.headers || {}) } }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || `HTTP ${res.status}`); } return res.json(); } function fmtDatum(iso) { if (!iso) return "—"; const d = new Date(iso); return `${d.getDate()}-${d.getMonth() + 1}-${d.getFullYear()}`; } function RolSelect({ value, onChange, opties, T, darkMode }) { const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener("mousedown", handler); return () => document.removeEventListener("mousedown", handler); }, []); return (
{open && (
{opties.map((o) => (
{ onChange(o); setOpen(false); }} style={{ padding: "9px 12px", fontSize: 13, cursor: "pointer", color: o === value ? TEAL_COLOR : T.text, background: o === value ? TEAL_BG : "transparent", }} onMouseEnter={(e) => e.currentTarget.style.background = "rgba(14,221,212,0.10)"} onMouseLeave={(e) => e.currentTarget.style.background = o === value ? TEAL_BG : "transparent"} > {o}
))}
)}
); } export default function GebruikersBeheer({ onClose }) { const { currentUser, darkMode, T } = useApp(); const [users, setUsers] = useState([]); const [loadErr, setLoadErr] = useState(""); const [showAdd, setShowAdd] = useState(false); const [editUser, setEditUser] = useState(null); // user object dat bewerkt wordt const [nieuwNaam, setNieuwNaam] = useState(""); const [nieuwEmail, setNieuwEmail] = useState(""); const [nieuwWw, setNieuwWw] = useState(""); const [nieuwRol, setNieuwRol] = useState("Viewer"); const [editNaam, setEditNaam] = useState(""); const [editEmail, setEditEmail] = useState(""); const [editWw, setEditWw] = useState(""); const [err, setErr] = useState(""); const [saving, setSaving] = useState(false); const rollen = ["Admin", "Bewerker", "Viewer"]; useEffect(() => { apiFetch("/users") .then(setUsers) .catch((e) => setLoadErr(e.message)); }, []); const is = { background: T.inputBg, border: `1px solid ${T.inputBorder}`, borderRadius: 8, color: T.text, padding: "8px 10px", fontSize: 13, outline: "none", boxSizing: "border-box", width: "100%", }; const addUser = async () => { if (!nieuwNaam.trim() || !nieuwEmail.trim() || !nieuwWw.trim()) { setErr("Naam, e-mail en wachtwoord zijn verplicht."); return; } setSaving(true); try { const u = await apiFetch("/users", { method: "POST", body: JSON.stringify({ naam: nieuwNaam, email: nieuwEmail, wachtwoord: nieuwWw, rol: nieuwRol }), }); setUsers((prev) => [...prev, u]); setNieuwNaam(""); setNieuwEmail(""); setNieuwWw(""); setErr(""); setShowAdd(false); } catch (e) { setErr(e.message); } finally { setSaving(false); } }; const openEdit = (u) => { setEditUser(u); setEditNaam(u.naam); setEditEmail(u.email); setEditWw(""); setErr(""); }; const saveEdit = async () => { if (!editNaam.trim() || !editEmail.trim()) { setErr("Naam en e-mail zijn verplicht."); return; } setSaving(true); try { const patch = { naam: editNaam.trim(), email: editEmail.trim() }; if (editWw) patch.wachtwoord = editWw; const updated = await apiFetch(`/users/${editUser.id}`, { method: "PATCH", body: JSON.stringify(patch), }); setUsers((prev) => prev.map((x) => x.id === editUser.id ? { ...x, ...updated } : x)); setEditUser(null); setErr(""); } catch (e) { setErr(e.message); } finally { setSaving(false); } }; const toggleActief = async (u) => { try { const updated = await apiFetch(`/users/${u.id}`, { method: "PATCH", body: JSON.stringify({ actief: !u.actief }), }); setUsers((prev) => prev.map((x) => x.id === u.id ? { ...x, ...updated } : x)); } catch (e) { setErr(e.message); } }; const removeUser = async (id) => { try { await apiFetch(`/users/${id}`, { method: "DELETE" }); setUsers((prev) => prev.filter((u) => u.id !== id)); } catch (e) { setErr(e.message); } }; const th = { padding: "8px 12px", fontSize: 11, color: T.muted, fontWeight: 600, textAlign: "left", borderBottom: `1px solid ${T.border}`, background: T.card, textTransform: "uppercase", letterSpacing: "0.05em", }; const td = { padding: "10px 12px", fontSize: 13, color: T.text, borderBottom: `1px solid ${T.border}` }; return (
e.stopPropagation()} style={{ background: T.popupBg, border: `1px solid ${T.border}`, borderRadius: 16, width: "100%", maxWidth: 700, maxHeight: "90vh", display: "flex", flexDirection: "column", boxShadow: "0 16px 48px rgba(0,0,0,0.4)", overflow: "hidden", }} > {/* Header */}
Gebruikersbeheer {users.length} gebruiker{users.length !== 1 ? "s" : ""}
{loadErr && (
{loadErr}
)} {/* Tabel */}
{users.map((u) => { const initials = u.naam.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2); const isSelf = u.id === currentUser?.id || u.email === currentUser?.email; return ( ); })}
Naam E-mail Aangemaakt Admin
{initials}
{u.naam} {isSelf && ( jij )}
{u.email} {fmtDatum(u.aangemaakt)} {u.rol === "Admin" ? ⭐ : — }
{!isSelf && ( <> )}
{err && (
{err}
)} {/* Bewerken modal */} {editUser && (
{editUser.naam} bewerken
setEditNaam(e.target.value)} style={is} /> setEditEmail(e.target.value)} style={is} /> setEditWw(e.target.value)} style={{ ...is, gridColumn: "1 / -1" }} />
)} {/* Nieuwe gebruiker */} {showAdd ? (
Nieuwe gebruiker
setNieuwNaam(e.target.value)} style={is} /> setNieuwEmail(e.target.value)} style={is} /> setNieuwWw(e.target.value)} style={is} />
) : ( )}
); }