2026-09-04 09:19:14 +02:00
|
|
|
|
import { useApp } from "../context/AppContext.jsx";
|
2026-09-15 14:02:17 +02:00
|
|
|
|
import { THEMAS } from "../constants/themas.js";
|
2026-09-04 09:19:14 +02:00
|
|
|
|
|
|
|
|
|
|
const BACKUP_ICON = (
|
|
|
|
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
|
|
|
|
|
|
<path d="M12 15V5M8 11l4-4 4 4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
|
|
|
|
|
<path d="M4 17v2a1 1 0 001 1h14a1 1 0 001-1v-2" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const RESTORE_ICON = (
|
|
|
|
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
|
|
|
|
|
|
<path d="M4 8a8 8 0 1 1 0 8" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/>
|
|
|
|
|
|
<polyline points="4,4 4,8 8,8" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
function ActionRow({ icon, title, description, onClick }) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
onClick={onClick}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: "flex", alignItems: "center", gap: 14,
|
|
|
|
|
|
padding: "12px 0", cursor: "pointer",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ color: "#94a3b8", flexShrink: 0 }}>{icon}</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{ fontWeight: 600, fontSize: 14, color: "inherit", marginBottom: 2 }}>{title}</div>
|
|
|
|
|
|
<div style={{ fontSize: 12, color: "#64748b" }}>{description}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function InstellingenModal({ onClose }) {
|
2026-09-15 14:02:17 +02:00
|
|
|
|
const { T, darkMode, setDarkMode, thema, setThema, doBackup, doRestore } = useApp();
|
2026-09-04 09:19:14 +02:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: "fixed", inset: 0, zIndex: 400,
|
|
|
|
|
|
background: "rgba(0,0,0,0.55)", backdropFilter: "blur(4px)",
|
|
|
|
|
|
display: "flex", alignItems: "center", justifyContent: "center",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
style={{
|
2026-09-04 09:19:23 +02:00
|
|
|
|
background: T.popupBg, border: `1px solid ${T.border}`,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
borderRadius: 16, padding: "22px 22px 8px",
|
|
|
|
|
|
width: 360, boxShadow: "0 16px 48px rgba(0,0,0,0.4)",
|
|
|
|
|
|
color: T.text,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 20 }}>
|
|
|
|
|
|
<span style={{ fontWeight: 700, fontSize: 16 }}>Instellingen</span>
|
|
|
|
|
|
<button onClick={onClose} style={{
|
|
|
|
|
|
background: "transparent", border: "none", cursor: "pointer",
|
|
|
|
|
|
color: T.muted, fontSize: 20, lineHeight: 1, padding: "0 4px",
|
|
|
|
|
|
}}>×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Dark mode rij */}
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
display: "flex", alignItems: "center", justifyContent: "space-between",
|
|
|
|
|
|
padding: "4px 0 16px",
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<span style={{ fontWeight: 600, fontSize: 14 }}>Dark mode</span>
|
2026-09-15 14:02:17 +02:00
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="switch"
|
|
|
|
|
|
aria-checked={darkMode}
|
|
|
|
|
|
aria-label="Donkere modus"
|
2026-09-04 09:19:14 +02:00
|
|
|
|
onClick={() => setDarkMode((d) => !d)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: 44, height: 24, borderRadius: 12, cursor: "pointer",
|
2026-09-15 14:02:17 +02:00
|
|
|
|
background: darkMode ? T.accent : "#334155",
|
|
|
|
|
|
border: "none", padding: 0,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
position: "relative", transition: "background 0.2s", flexShrink: 0,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-09-15 14:02:17 +02:00
|
|
|
|
<span style={{
|
2026-09-04 09:19:14 +02:00
|
|
|
|
position: "absolute", top: 3, borderRadius: "50%",
|
|
|
|
|
|
width: 18, height: 18, background: "#fff",
|
|
|
|
|
|
left: darkMode ? 23 : 3,
|
|
|
|
|
|
transition: "left 0.2s",
|
|
|
|
|
|
boxShadow: "0 1px 4px rgba(0,0,0,0.3)",
|
|
|
|
|
|
}} />
|
2026-09-15 14:02:17 +02:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Kleurthema */}
|
|
|
|
|
|
<div style={{ padding: "0 0 18px" }}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
display: "flex", alignItems: "baseline", justifyContent: "space-between",
|
|
|
|
|
|
gap: 10, marginBottom: 10,
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<span style={{ fontWeight: 600, fontSize: 14 }}>Kleurthema</span>
|
|
|
|
|
|
<span style={{ fontSize: 12, color: T.muted }}>{T.themaNaam}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/* Drie kolommen: negen thema's vullen zo precies drie rijen, zonder
|
|
|
|
|
|
een halve rij onderaan. */}
|
|
|
|
|
|
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8 }}>
|
|
|
|
|
|
{THEMAS.map((t) => {
|
|
|
|
|
|
const gekozen = t.id === thema;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={t.id}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setThema(t.id)}
|
|
|
|
|
|
title={t.naam}
|
|
|
|
|
|
aria-label={`Thema ${t.naam}`}
|
|
|
|
|
|
aria-pressed={gekozen}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
height: 30, borderRadius: 9, cursor: "pointer", padding: 0,
|
|
|
|
|
|
background: `linear-gradient(135deg, ${t.hoofd}, ${t.licht})`,
|
|
|
|
|
|
// De gekozen kleur krijgt een ring in de kaartkleur plus een
|
|
|
|
|
|
// rand, zodat hij ook opvalt als het thema zelf licht is.
|
|
|
|
|
|
border: `2px solid ${gekozen ? T.text : "transparent"}`,
|
|
|
|
|
|
boxShadow: gekozen ? `0 0 0 2px ${T.accentLicht}` : "none",
|
|
|
|
|
|
transition: "box-shadow 0.15s, border-color 0.15s",
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
2026-09-04 09:19:14 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ height: 1, background: T.border, margin: "0 -22px" }} />
|
|
|
|
|
|
|
|
|
|
|
|
{/* Acties */}
|
|
|
|
|
|
<div style={{ padding: "4px 0 10px" }}>
|
|
|
|
|
|
<ActionRow
|
|
|
|
|
|
icon={BACKUP_ICON}
|
|
|
|
|
|
title="Backup maken"
|
|
|
|
|
|
description="Download een volledige backup als ZIP"
|
|
|
|
|
|
onClick={() => { doBackup(); onClose(); }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div style={{ height: 1, background: T.border }} />
|
|
|
|
|
|
<ActionRow
|
|
|
|
|
|
icon={RESTORE_ICON}
|
|
|
|
|
|
title="Backup herstellen"
|
|
|
|
|
|
description="Herstel data vanuit een backup-bestand"
|
|
|
|
|
|
onClick={() => { doRestore(); onClose(); }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|