vormgeving: compleet thema (schoolnaam + kleuren) + logo-rechten gefixt
This commit is contained in:
parent
d8c4523dc0
commit
dd52b81b02
11 changed files with 169 additions and 27 deletions
11
ROADMAP.md
11
ROADMAP.md
|
|
@ -46,10 +46,13 @@ volgt.
|
||||||
koptekst, **kleurenlegenda** en **elke dag op nieuwe pagina** — plus
|
koptekst, **kleurenlegenda** en **elke dag op nieuwe pagina** — plus
|
||||||
**papierformaat** (A4/A3/A5/Letter) en oriëntatie (liggend/staand).
|
**papierformaat** (A4/A3/A5/Letter) en oriëntatie (liggend/staand).
|
||||||
Afdruk-koptekst met logo + schooljaar + week + afdrukdatum.
|
Afdruk-koptekst met logo + schooljaar + week + afdrukdatum.
|
||||||
- [x] **Module Huisstijl** (`huisstijl`, default aan): schoollogo uploaden via
|
- [x] **Module Vormgeving / thema** (`huisstijl`, default aan): schoollogo
|
||||||
Instellingen (PNG/SVG/JPG/GIF/WebP) met **kwaliteitscheck** (bestandstype,
|
uploaden via Instellingen (PNG/SVG/JPG/GIF/WebP) met **kwaliteitscheck**
|
||||||
resolutie, transparantie/achtergrond — waarschuwt bij gevulde/witte
|
(bestandstype, resolutie, transparantie/achtergrond). Logo verschijnt in
|
||||||
achtergrond). Logo verschijnt in elke roosterweergave en op afdrukken.
|
elke roosterweergave en op afdrukken. Daarnaast **schoolnaam** (in de balk
|
||||||
|
+ op afdrukken) en **thema-kleuren** (primair + accent) die app-breed
|
||||||
|
worden toegepast als CSS-variabelen. Geüploade media wereldwijd-leesbaar
|
||||||
|
(FILE_UPLOAD_PERMISSIONS) en self-healing volume-rechten via gosu-entrypoint.
|
||||||
|
|
||||||
- [x] **Vakkleur als vlak** (toggle 🎨, onthouden per browser): vak volledig in de
|
- [x] **Vakkleur als vlak** (toggle 🎨, onthouden per browser): vak volledig in de
|
||||||
activiteitskleur i.p.v. een streepje, met automatisch leesbare tekstkleur
|
activiteitskleur i.p.v. een streepje, met automatisch leesbare tekstkleur
|
||||||
|
|
|
||||||
|
|
@ -155,9 +155,13 @@ USE_TZ = True
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
STATIC_ROOT = os.environ.get("DJANGO_STATIC_ROOT", str(BASE_DIR / "staticfiles"))
|
STATIC_ROOT = os.environ.get("DJANGO_STATIC_ROOT", str(BASE_DIR / "staticfiles"))
|
||||||
|
|
||||||
# Geüploade bestanden (o.a. pictogrammen bij vakken).
|
# Geüploade bestanden (o.a. pictogrammen bij vakken, schoollogo).
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = os.environ.get("DJANGO_MEDIA_ROOT", str(BASE_DIR / "media"))
|
MEDIA_ROOT = os.environ.get("DJANGO_MEDIA_ROOT", str(BASE_DIR / "media"))
|
||||||
|
# Geüploade bestanden wereldwijd-leesbaar wegschrijven, anders kan nginx (die als
|
||||||
|
# een andere gebruiker draait) ze niet serveren → gebroken afbeeldingen.
|
||||||
|
FILE_UPLOAD_PERMISSIONS = 0o644
|
||||||
|
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
|
||||||
|
|
||||||
# E-mail (voor o.a. wachtwoordreset). Zonder SMTP-config gaat e-mail naar de
|
# E-mail (voor o.a. wachtwoordreset). Zonder SMTP-config gaat e-mail naar de
|
||||||
# console-log (handig voor test); zet DJANGO_EMAIL_BACKEND/EMAIL_* in productie.
|
# console-log (handig voor test); zet DJANGO_EMAIL_BACKEND/EMAIL_* in productie.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ class HuisstijlConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = "modules.huisstijl"
|
name = "modules.huisstijl"
|
||||||
label = "module_huisstijl"
|
label = "module_huisstijl"
|
||||||
verbose_name = "Module: Logo"
|
verbose_name = "Module: Vormgeving"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from plugins.registry import MenuItem, ModuleSpec, registry
|
from plugins.registry import MenuItem, ModuleSpec, registry
|
||||||
|
|
@ -13,7 +13,7 @@ class HuisstijlConfig(AppConfig):
|
||||||
registry.register(
|
registry.register(
|
||||||
ModuleSpec(
|
ModuleSpec(
|
||||||
key="huisstijl",
|
key="huisstijl",
|
||||||
name="Logo",
|
name="Vormgeving",
|
||||||
description=(
|
description=(
|
||||||
"Toont het schoollogo in elke roosterweergave en op afdrukken. "
|
"Toont het schoollogo in elke roosterweergave en op afdrukken. "
|
||||||
"Logo te uploaden via Instellingen, met een kwaliteitscheck "
|
"Logo te uploaden via Instellingen, met een kwaliteitscheck "
|
||||||
|
|
@ -24,7 +24,7 @@ class HuisstijlConfig(AppConfig):
|
||||||
core=False,
|
core=False,
|
||||||
default_enabled=True,
|
default_enabled=True,
|
||||||
menu_items=(
|
menu_items=(
|
||||||
MenuItem("Logo", "/logo", icon="badge", order=74, group="Instellingen"),
|
MenuItem("Vormgeving", "/vormgeving", icon="badge", order=74, group="Instellingen"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
26
backend/modules/huisstijl/migrations/0002_thema_velden.py
Normal file
26
backend/modules/huisstijl/migrations/0002_thema_velden.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("module_huisstijl", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="huisstijl",
|
||||||
|
name="schoolnaam",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=120, verbose_name="schoolnaam"),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="huisstijl",
|
||||||
|
name="primaire_kleur",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=9, verbose_name="primaire kleur"),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="huisstijl",
|
||||||
|
name="accent_kleur",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=9, verbose_name="accentkleur"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -2,10 +2,14 @@ from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Huisstijl(models.Model):
|
class Huisstijl(models.Model):
|
||||||
"""Eén-rij-instelling met het schoollogo dat in elke roosterweergave en op
|
"""Eén-rij-instelling met de vormgeving (thema) van de school: logo,
|
||||||
afdrukken getoond wordt."""
|
schoolnaam en kleuren. Wordt in elke roosterweergave en op afdrukken
|
||||||
|
toegepast."""
|
||||||
|
|
||||||
logo = models.FileField("logo", upload_to="huisstijl/", null=True, blank=True)
|
logo = models.FileField("logo", upload_to="huisstijl/", null=True, blank=True)
|
||||||
|
schoolnaam = models.CharField("schoolnaam", max_length=120, blank=True, default="")
|
||||||
|
primaire_kleur = models.CharField("primaire kleur", max_length=9, blank=True, default="")
|
||||||
|
accent_kleur = models.CharField("accentkleur", max_length=9, blank=True, default="")
|
||||||
aangepast_op = models.DateTimeField(auto_now=True)
|
aangepast_op = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ class HuisstijlSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Huisstijl
|
model = Huisstijl
|
||||||
fields = ["id", "logo", "aangepast_op"]
|
fields = ["id", "logo", "schoolnaam", "primaire_kleur", "accent_kleur", "aangepast_op"]
|
||||||
|
|
|
||||||
|
|
@ -109,25 +109,39 @@ def huisstijl(request):
|
||||||
return Response(HuisstijlSerializer(obj, context={"request": request}).data)
|
return Response(HuisstijlSerializer(obj, context={"request": request}).data)
|
||||||
|
|
||||||
if request.method in ("PUT", "POST"):
|
if request.method in ("PUT", "POST"):
|
||||||
|
analyse = None
|
||||||
|
# Thema-tekstvelden (kleuren/naam) — uit JSON of form-data.
|
||||||
|
for veld in ("schoolnaam", "primaire_kleur", "accent_kleur"):
|
||||||
|
if veld in request.data:
|
||||||
|
waarde = (request.data.get(veld) or "").strip()
|
||||||
|
if veld.endswith("_kleur") and waarde and not _is_kleur(waarde):
|
||||||
|
return Response({"detail": f"Ongeldige kleur voor {veld}: gebruik een hex-code zoals #2563eb."}, status=400)
|
||||||
|
setattr(obj, veld, waarde)
|
||||||
|
# Optioneel een nieuw logo.
|
||||||
f = request.FILES.get("logo")
|
f = request.FILES.get("logo")
|
||||||
if not f:
|
if f:
|
||||||
return Response({"detail": "Geen bestand ontvangen (veld 'logo')."}, status=400)
|
|
||||||
try:
|
try:
|
||||||
analyse = analyseer_logo(f)
|
analyse = analyseer_logo(f)
|
||||||
except Exception as e: # noqa: BLE001 — geef een leesbare reden i.p.v. 500.
|
except Exception as e: # noqa: BLE001 — geef een leesbare reden i.p.v. 500.
|
||||||
return Response({"detail": f"Kon de afbeelding niet verwerken: {e}"}, status=400)
|
return Response({"detail": f"Kon de afbeelding niet verwerken: {e}"}, status=400)
|
||||||
if not analyse["ok"]:
|
if not analyse["ok"]:
|
||||||
return Response({"detail": " ".join(analyse["fouten"]), "kwaliteit": analyse}, status=400)
|
return Response({"detail": " ".join(analyse["fouten"]), "kwaliteit": analyse}, status=400)
|
||||||
try:
|
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
if obj.logo:
|
if obj.logo:
|
||||||
obj.logo.delete(save=False)
|
obj.logo.delete(save=False)
|
||||||
obj.logo = f
|
obj.logo = f
|
||||||
|
try:
|
||||||
obj.save()
|
obj.save()
|
||||||
except Exception as e: # noqa: BLE001
|
except Exception as e: # noqa: BLE001
|
||||||
return Response({"detail": f"Opslaan van het logo mislukte: {e}"}, status=400)
|
return Response({"detail": f"Opslaan mislukte: {e}"}, status=400)
|
||||||
data = HuisstijlSerializer(obj, context={"request": request}).data
|
data = HuisstijlSerializer(obj, context={"request": request}).data
|
||||||
|
if analyse is not None:
|
||||||
data["kwaliteit"] = analyse
|
data["kwaliteit"] = analyse
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
return Response(HuisstijlSerializer(obj, context={"request": request}).data)
|
return Response(HuisstijlSerializer(obj, context={"request": request}).data)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_kleur(s):
|
||||||
|
import re
|
||||||
|
return bool(re.fullmatch(r"#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})", s or ""))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { getMe, login, logout, getModules, setModuleState, wachtwoordVergeten } from "./api.js";
|
import { getMe, login, logout, getModules, setModuleState, wachtwoordVergeten, getHuisstijl } from "./api.js";
|
||||||
import Logo from "./Logo.jsx";
|
import Logo from "./Logo.jsx";
|
||||||
import {
|
import {
|
||||||
PersonenPage, FunctiesPage,
|
PersonenPage, FunctiesPage,
|
||||||
|
|
@ -47,9 +47,29 @@ const PAGES = {
|
||||||
"/vak-wizard": VakWizardPage,
|
"/vak-wizard": VakWizardPage,
|
||||||
"/personen-wizard": PersonenWizardPage,
|
"/personen-wizard": PersonenWizardPage,
|
||||||
"/auto-rooster": AutoRoosterPage,
|
"/auto-rooster": AutoRoosterPage,
|
||||||
"/logo": HuisstijlPage,
|
"/vormgeving": HuisstijlPage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Thema (vormgeving) app-breed toepassen via CSS-variabelen.
|
||||||
|
function hexNaarRgba(hex, a) {
|
||||||
|
if (!hex) return "";
|
||||||
|
const m = hex.replace("#", "");
|
||||||
|
const v = m.length === 3 ? m.split("").map((c) => c + c).join("") : m;
|
||||||
|
if (v.length < 6) return "";
|
||||||
|
const r = parseInt(v.slice(0, 2), 16), g = parseInt(v.slice(2, 4), 16), b = parseInt(v.slice(4, 6), 16);
|
||||||
|
if ([r, g, b].some(Number.isNaN)) return "";
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
|
}
|
||||||
|
export function pasThemaToe(h) {
|
||||||
|
const root = document.documentElement;
|
||||||
|
if (h && h.primaire_kleur) {
|
||||||
|
root.style.setProperty("--brand", h.primaire_kleur);
|
||||||
|
const soft = hexNaarRgba(h.primaire_kleur, 0.14);
|
||||||
|
if (soft) root.style.setProperty("--brand-soft", soft);
|
||||||
|
}
|
||||||
|
if (h && h.accent_kleur) root.style.setProperty("--accent", h.accent_kleur);
|
||||||
|
}
|
||||||
|
|
||||||
const RESET_PARAMS = (() => {
|
const RESET_PARAMS = (() => {
|
||||||
try {
|
try {
|
||||||
const q = new URLSearchParams(window.location.search);
|
const q = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -181,6 +201,11 @@ function Shell({ user, onLogout }) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [cogOpen, setCogOpen] = useState(false);
|
const [cogOpen, setCogOpen] = useState(false);
|
||||||
const [openMenu, setOpenMenu] = useState(null);
|
const [openMenu, setOpenMenu] = useState(null);
|
||||||
|
const [schoolnaam, setSchoolnaam] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getHuisstijl().then((h) => { pasThemaToe(h); setSchoolnaam(h && h.schoolnaam ? h.schoolnaam : ""); }).catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -338,6 +363,7 @@ function Shell({ user, onLogout }) {
|
||||||
<div className="brand">
|
<div className="brand">
|
||||||
<span className="brand-logo"><Logo size={30} /></span>
|
<span className="brand-logo"><Logo size={30} /></span>
|
||||||
<span className="brand-name">Roosterwijs</span>
|
<span className="brand-name">Roosterwijs</span>
|
||||||
|
{schoolnaam && <span className="brand-schoolnaam">· {schoolnaam}</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="topbar-user">
|
<div className="topbar-user">
|
||||||
<span className="muted small">{user.username}</span>
|
<span className="muted small">{user.username}</span>
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ export const logout = () => request("POST", "/auth/logout/");
|
||||||
|
|
||||||
// --- Huisstijl (schoollogo) -----------------------------------------------
|
// --- Huisstijl (schoollogo) -----------------------------------------------
|
||||||
export const getHuisstijl = () => request("GET", "/huisstijl/");
|
export const getHuisstijl = () => request("GET", "/huisstijl/");
|
||||||
|
export const opslaanVormgeving = (data) => request("PUT", "/huisstijl/", data);
|
||||||
export const verwijderLogo = () => request("DELETE", "/huisstijl/");
|
export const verwijderLogo = () => request("DELETE", "/huisstijl/");
|
||||||
export async function uploadLogo(file) {
|
export async function uploadLogo(file) {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
getHuisstijl,
|
getHuisstijl,
|
||||||
uploadLogo,
|
uploadLogo,
|
||||||
verwijderLogo,
|
verwijderLogo,
|
||||||
|
opslaanVormgeving,
|
||||||
} from "./api.js";
|
} from "./api.js";
|
||||||
|
|
||||||
const ROLLEN = [
|
const ROLLEN = [
|
||||||
|
|
@ -2830,7 +2831,7 @@ export function RoosterScherm({ mode }) {
|
||||||
<div className="inst-body"><p className="muted small">Vrije locatie-aanduidingen (bv. gymzaal, zwembad).</p><LocatiesPage /></div>
|
<div className="inst-body"><p className="muted small">Vrije locatie-aanduidingen (bv. gymzaal, zwembad).</p><LocatiesPage /></div>
|
||||||
</details>
|
</details>
|
||||||
<details className="inst-sectie">
|
<details className="inst-sectie">
|
||||||
<summary><span className="ico">🖼️</span> Logo</summary>
|
<summary><span className="ico">🖼️</span> Vormgeving</summary>
|
||||||
<div className="inst-body"><HuisstijlPage /></div>
|
<div className="inst-body"><HuisstijlPage /></div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3281,7 +3282,22 @@ export function RoosterLogo({ print = false }) {
|
||||||
const [logo, setLogo] = useState(null);
|
const [logo, setLogo] = useState(null);
|
||||||
useEffect(() => { getHuisstijl().then((h) => setLogo(h && h.logo ? h.logo : null)).catch(() => {}); }, []);
|
useEffect(() => { getHuisstijl().then((h) => setLogo(h && h.logo ? h.logo : null)).catch(() => {}); }, []);
|
||||||
if (!logo) return null;
|
if (!logo) return null;
|
||||||
return <img src={logo} alt="Logo" className={print ? "rooster-logo print-logo" : "rooster-logo no-print"} />;
|
return <img src={logo} alt="" className={print ? "rooster-logo print-logo" : "rooster-logo no-print"} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pas thema-kleuren direct toe (zelfde logica als App.pasThemaToe; hier
|
||||||
|
// gedupliceerd om een circulaire import te vermijden).
|
||||||
|
function _themaDirect(h) {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const rgba = (hex, a) => {
|
||||||
|
if (!hex) return "";
|
||||||
|
const m = hex.replace("#", ""); const v = m.length === 3 ? m.split("").map((c) => c + c).join("") : m;
|
||||||
|
if (v.length < 6) return "";
|
||||||
|
const r = parseInt(v.slice(0, 2), 16), g = parseInt(v.slice(2, 4), 16), b = parseInt(v.slice(4, 6), 16);
|
||||||
|
return [r, g, b].some(Number.isNaN) ? "" : `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
|
};
|
||||||
|
if (h && h.primaire_kleur) { root.style.setProperty("--brand", h.primaire_kleur); const s = rgba(h.primaire_kleur, 0.14); if (s) root.style.setProperty("--brand-soft", s); }
|
||||||
|
if (h && h.accent_kleur) root.style.setProperty("--accent", h.accent_kleur);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HuisstijlPage() {
|
export function HuisstijlPage() {
|
||||||
|
|
@ -3289,8 +3305,33 @@ export function HuisstijlPage() {
|
||||||
const [kwaliteit, setKwaliteit] = useState(null);
|
const [kwaliteit, setKwaliteit] = useState(null);
|
||||||
const [bezig, setBezig] = useState(false);
|
const [bezig, setBezig] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const [form, setForm] = useState({ schoolnaam: "", primaire_kleur: "", accent_kleur: "" });
|
||||||
|
const [opslaanOk, setOpslaanOk] = useState(false);
|
||||||
|
|
||||||
useEffect(() => { getHuisstijl().then(setHuis).catch((e) => setError(e.message)); }, []);
|
useEffect(() => { getHuisstijl().then((h) => { setHuis(h); setForm({ schoolnaam: h.schoolnaam || "", primaire_kleur: h.primaire_kleur || "", accent_kleur: h.accent_kleur || "" }); }).catch((e) => setError(e.message)); }, []);
|
||||||
|
|
||||||
|
async function opslaanThema() {
|
||||||
|
setBezig(true); setError(null); setOpslaanOk(false);
|
||||||
|
try {
|
||||||
|
const r = await opslaanVormgeving(form);
|
||||||
|
setHuis(r);
|
||||||
|
_themaDirect(r);
|
||||||
|
setOpslaanOk(true);
|
||||||
|
} catch (e) { setError(e.message); }
|
||||||
|
setBezig(false);
|
||||||
|
}
|
||||||
|
async function herstelKleuren() {
|
||||||
|
setForm((f) => ({ ...f, primaire_kleur: "", accent_kleur: "" }));
|
||||||
|
setBezig(true); setError(null);
|
||||||
|
try {
|
||||||
|
const r = await opslaanVormgeving({ ...form, primaire_kleur: "", accent_kleur: "" });
|
||||||
|
setHuis(r);
|
||||||
|
document.documentElement.style.removeProperty("--brand");
|
||||||
|
document.documentElement.style.removeProperty("--brand-soft");
|
||||||
|
document.documentElement.style.removeProperty("--accent");
|
||||||
|
} catch (e) { setError(e.message); }
|
||||||
|
setBezig(false);
|
||||||
|
}
|
||||||
|
|
||||||
async function kies(e) {
|
async function kies(e) {
|
||||||
const f = e.target.files && e.target.files[0];
|
const f = e.target.files && e.target.files[0];
|
||||||
|
|
@ -3316,6 +3357,21 @@ export function HuisstijlPage() {
|
||||||
return (
|
return (
|
||||||
<div className="huisstijl">
|
<div className="huisstijl">
|
||||||
<Foutmelding msg={error} />
|
<Foutmelding msg={error} />
|
||||||
|
|
||||||
|
<h3 className="vorm-kop">Schoolnaam & kleuren</h3>
|
||||||
|
<p className="muted small">De schoolnaam verschijnt in de balk en op afdrukken. De kleuren bepalen het thema van de hele app (knoppen, tabs, accenten).</p>
|
||||||
|
<div className="vorm-grid">
|
||||||
|
<label className="veld-kol"><span className="veld-kop">Schoolnaam</span><input placeholder="bv. De Regenboog" value={form.schoolnaam} onChange={(e) => setForm({ ...form, schoolnaam: e.target.value })} /></label>
|
||||||
|
<label className="veld-kol"><span className="veld-kop">Primaire kleur</span><input type="color" value={form.primaire_kleur || "#2563eb"} onChange={(e) => setForm({ ...form, primaire_kleur: e.target.value })} /></label>
|
||||||
|
<label className="veld-kol"><span className="veld-kop">Accentkleur</span><input type="color" value={form.accent_kleur || "#2563eb"} onChange={(e) => setForm({ ...form, accent_kleur: e.target.value })} /></label>
|
||||||
|
</div>
|
||||||
|
<div className="editor-actions">
|
||||||
|
<button type="button" className="btn primary" onClick={opslaanThema} disabled={bezig}>{bezig ? "Bezig…" : "Opslaan"}</button>
|
||||||
|
<button type="button" className="btn" onClick={herstelKleuren} disabled={bezig}>Standaardkleuren</button>
|
||||||
|
{opslaanOk && <span className="wm-status ok">Opgeslagen ✓</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="vorm-kop">Logo</h3>
|
||||||
<p className="muted small">Het logo verschijnt in elke roosterweergave en op afdrukken. Gebruik bij voorkeur een <b>transparante PNG</b> of <b>SVG</b>; een logo met een gevulde (witte) achtergrond valt op een gekleurde balk lelijk op.</p>
|
<p className="muted small">Het logo verschijnt in elke roosterweergave en op afdrukken. Gebruik bij voorkeur een <b>transparante PNG</b> of <b>SVG</b>; een logo met een gevulde (witte) achtergrond valt op een gekleurde balk lelijk op.</p>
|
||||||
<div className="logo-vak">
|
<div className="logo-vak">
|
||||||
<div className="logo-preview" title="Voorbeeld op een geruite achtergrond (zo zie je transparantie)">
|
<div className="logo-preview" title="Voorbeeld op een geruite achtergrond (zo zie je transparantie)">
|
||||||
|
|
|
||||||
|
|
@ -603,6 +603,14 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
||||||
.logo-preview img { max-width: 200px; max-height: 104px; object-fit: contain; }
|
.logo-preview img { max-width: 200px; max-height: 104px; object-fit: contain; }
|
||||||
.logo-acties { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
|
.logo-acties { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
|
||||||
.logo-acties .btn { cursor: pointer; }
|
.logo-acties .btn { cursor: pointer; }
|
||||||
|
.vorm-kop { margin: 18px 0 4px; font-size: 15px; }
|
||||||
|
.huisstijl > .vorm-kop:first-of-type { margin-top: 0; }
|
||||||
|
.vorm-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; align-items: end; margin: 8px 0; }
|
||||||
|
.vorm-grid input[type="color"] { width: 64px; height: 36px; padding: 2px; cursor: pointer; }
|
||||||
|
.brand-schoolnaam { color: var(--muted); font-weight: 600; font-size: 14px; }
|
||||||
|
/* Accentkleur (uit de vormgeving) op een paar zichtbare plekken */
|
||||||
|
.tab.on { border-bottom-color: var(--accent, var(--brand)); }
|
||||||
|
.top-tab.on, .nav-trigger.on { box-shadow: inset 0 -2px 0 var(--accent, var(--brand)); }
|
||||||
|
|
||||||
/* Kleurenlegenda (alleen bij afdrukken) */
|
/* Kleurenlegenda (alleen bij afdrukken) */
|
||||||
.kleur-legenda { margin-top: 10px; padding-top: 8px; border-top: 1px solid #ccc; }
|
.kleur-legenda { margin-top: 10px; padding-top: 8px; border-top: 1px solid #ccc; }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue