Rooster gesplitst in weergave + Roostermaker, printmodule afgebouwd (subtiele Afdrukken-knop met print-CSS)
This commit is contained in:
parent
3318ae601e
commit
e8e0022359
7 changed files with 89 additions and 30 deletions
|
|
@ -18,6 +18,12 @@ rustiger is). De zijbalk groepeert items onder inklapbare submenu's (Personen,
|
||||||
Groepen) en heeft onderaan een subtiel **Instellingen**-blok met Gebruikers en
|
Groepen) en heeft onderaan een subtiel **Instellingen**-blok met Gebruikers en
|
||||||
Modulebeheer.
|
Modulebeheer.
|
||||||
|
|
||||||
|
Er zijn twee rooster-schermen: **Rooster** (alleen-lezen weergave, om te bekijken
|
||||||
|
en af te drukken) en **Roostermaker** (de aanpasser met drag-and-drop, tabs en
|
||||||
|
conflictdetectie). De module **Printen/Exporteren** voegt — als die aanstaat — een
|
||||||
|
subtiele **Afdrukken**-knop toe aan beide; die drukt het weekrooster
|
||||||
|
printvriendelijk af (of bewaart als PDF via de printdialoog).
|
||||||
|
|
||||||
Optionele module **Leerplein**: koppelt subgroepen aan groepen en bundelt
|
Optionele module **Leerplein**: koppelt subgroepen aan groepen en bundelt
|
||||||
groepen, subgroepen én losse leerlingen onder een overkoepelend leerplein.
|
groepen, subgroepen én losse leerlingen onder een overkoepelend leerplein.
|
||||||
Optionele module **Stage**: interne en externe stages van leerlingen — beide met
|
Optionele module **Stage**: interne en externe stages van leerlingen — beide met
|
||||||
|
|
|
||||||
16
ROADMAP.md
16
ROADMAP.md
|
|
@ -48,6 +48,22 @@ volgt.
|
||||||
(de betreffende blokken vervallen voor die leerling).
|
(de betreffende blokken vervallen voor die leerling).
|
||||||
- [ ] Stage instelbaar **per blok, per dagdeel of per dag**.
|
- [ ] Stage instelbaar **per blok, per dagdeel of per dag**.
|
||||||
|
|
||||||
|
## Module: Urenoverzicht
|
||||||
|
|
||||||
|
- [ ] **Module die berekent hoeveel uur een groep per vak krijgt.**
|
||||||
|
- [ ] Optie om dit **per leerling** te tonen, **inclusief stage-uren**.
|
||||||
|
- [ ] **Geschiedenis** van dit overzicht **per leerling** tonen (verloop over de
|
||||||
|
tijd).
|
||||||
|
- [ ] **Vrije dagen tellen niet mee** in deze urenberekening.
|
||||||
|
|
||||||
|
## Roostermaker — vrije dagen & blok vervallen
|
||||||
|
|
||||||
|
- [ ] **Vrije/vakantie-/studiedagen kunnen niet gebruikt worden** in de
|
||||||
|
Roostermaker (geen blokken plannen op die dagen).
|
||||||
|
- [ ] De actie **"Deze dag laten vervallen"** subtieler wegwerken **en de tekst
|
||||||
|
corrigeren** — het is per *blok*, niet per dag.
|
||||||
|
- [ ] Mogelijkheid om **een hele dag op vervallen** te zetten (i.p.v. per blok).
|
||||||
|
|
||||||
## Later
|
## Later
|
||||||
|
|
||||||
- [ ] **Module: automatisch rooster genereren.** De gebruiker geeft vereisten/
|
- [ ] **Module: automatisch rooster genereren.** De gebruiker geeft vereisten/
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,9 @@ class CoreConfig(AppConfig):
|
||||||
category="Kern",
|
category="Kern",
|
||||||
core=True,
|
core=True,
|
||||||
menu_items=(
|
menu_items=(
|
||||||
# Hoofdniveau (Schooljaar zit nu als tab in het Rooster)
|
# Hoofdniveau: Rooster = weergave, Roostermaker = aanpasser.
|
||||||
MenuItem("Rooster", "/rooster", icon="calendar", order=10),
|
MenuItem("Rooster", "/rooster", icon="calendar", order=10),
|
||||||
|
MenuItem("Roostermaker", "/roostermaker", icon="calendar", order=12),
|
||||||
# Submenu Personen
|
# Submenu Personen
|
||||||
MenuItem("Personen", "/personen", icon="users", order=20, group="Personen"),
|
MenuItem("Personen", "/personen", icon="users", order=20, group="Personen"),
|
||||||
MenuItem("Functies", "/functies", icon="badge", order=25, group="Personen"),
|
MenuItem("Functies", "/functies", icon="badge", order=25, group="Personen"),
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,16 @@ class PrintingConfig(AppConfig):
|
||||||
key="printing",
|
key="printing",
|
||||||
name="Printen / Exporteren",
|
name="Printen / Exporteren",
|
||||||
description=(
|
description=(
|
||||||
"Roosters afdrukken of als PDF exporteren, per groep, "
|
"Voegt een subtiele 'Afdrukken'-knop toe aan het Rooster en de "
|
||||||
"leerling of leerkracht."
|
"Roostermaker, die het weekrooster printvriendelijk afdrukt "
|
||||||
|
"(of als PDF bewaart via de printdialoog)."
|
||||||
),
|
),
|
||||||
version="0.1.0",
|
version="0.1.0",
|
||||||
category="Uitvoer",
|
category="Uitvoer",
|
||||||
core=False,
|
core=False,
|
||||||
default_enabled=True,
|
default_enabled=True,
|
||||||
menu_items=(
|
# Geen eigen menu-item: de knop verschijnt in de rooster-schermen
|
||||||
MenuItem("Afdrukken", "/afdrukken", icon="printer", order=200),
|
# zodra deze module aanstaat.
|
||||||
),
|
menu_items=(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ import { getMe, login, logout, getModules, setModuleState } from "./api.js";
|
||||||
import Logo from "./Logo.jsx";
|
import Logo from "./Logo.jsx";
|
||||||
import {
|
import {
|
||||||
GroepenPage, PersonenPage, SubgroepenPage, FunctiesPage,
|
GroepenPage, PersonenPage, SubgroepenPage, FunctiesPage,
|
||||||
ActiviteitenPage, LocatiesPage, TijdslotenPage, SchooljaarPage, RoosterPage,
|
ActiviteitenPage, LocatiesPage, TijdslotenPage, SchooljaarPage,
|
||||||
|
RoosterWeergavePage, RoostermakerPage,
|
||||||
AfwezigheidPage, LeerpleinPage, StagePage,
|
AfwezigheidPage, LeerpleinPage, StagePage,
|
||||||
} from "./pages.jsx";
|
} from "./pages.jsx";
|
||||||
|
|
||||||
|
|
@ -16,7 +17,8 @@ const ICONS = {
|
||||||
// Koppel een menu-pad aan een echt scherm. Onbekende paden tonen een
|
// Koppel een menu-pad aan een echt scherm. Onbekende paden tonen een
|
||||||
// placeholder (worden in een latere fase ingevuld).
|
// placeholder (worden in een latere fase ingevuld).
|
||||||
const PAGES = {
|
const PAGES = {
|
||||||
"/rooster": RoosterPage,
|
"/rooster": RoosterWeergavePage,
|
||||||
|
"/roostermaker": RoostermakerPage,
|
||||||
"/personen": PersonenPage,
|
"/personen": PersonenPage,
|
||||||
"/functies": FunctiesPage,
|
"/functies": FunctiesPage,
|
||||||
"/groepen": GroepenPage,
|
"/groepen": GroepenPage,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
createResource,
|
createResource,
|
||||||
updateResource,
|
updateResource,
|
||||||
deleteResource,
|
deleteResource,
|
||||||
|
getModules,
|
||||||
} from "./api.js";
|
} from "./api.js";
|
||||||
|
|
||||||
const ROLLEN = [
|
const ROLLEN = [
|
||||||
|
|
@ -1013,9 +1014,12 @@ function maandagVan(d) { const x = new Date(d); x.setHours(0, 0, 0, 0); const wd
|
||||||
function plusDagen(d, n) { const x = new Date(d); x.setDate(x.getDate() + n); return x; }
|
function plusDagen(d, n) { const x = new Date(d); x.setDate(x.getDate() + n); return x; }
|
||||||
|
|
||||||
/* ---------------------------------- Rooster ------------------------------- */
|
/* ---------------------------------- Rooster ------------------------------- */
|
||||||
// Weekweergave met effectief rooster (vrije dagen, afwezigheid, uitzonderingen)
|
// Eén component met twee modi:
|
||||||
// en drag-and-drop (muis + touch) om een blok naar een ander tijdslot te slepen.
|
// - mode="view" -> Rooster (alleen-lezen weergave, om te bekijken/printen)
|
||||||
export function RoosterPage() {
|
// - mode="edit" -> Roostermaker (volledige bewerking + drag-and-drop)
|
||||||
|
export function RoosterScherm({ mode }) {
|
||||||
|
const edit = mode === "edit";
|
||||||
|
const [printAan, setPrintAan] = useState(false);
|
||||||
const [schooljaren, setSchooljaren] = useState([]);
|
const [schooljaren, setSchooljaren] = useState([]);
|
||||||
const [schooljaar, setSchooljaar] = useState("");
|
const [schooljaar, setSchooljaar] = useState("");
|
||||||
const [tijdsloten, setTijdsloten] = useState([]);
|
const [tijdsloten, setTijdsloten] = useState([]);
|
||||||
|
|
@ -1052,6 +1056,8 @@ export function RoosterPage() {
|
||||||
listResource("subgroepen").then(setSubgroepen).catch(() => {});
|
listResource("subgroepen").then(setSubgroepen).catch(() => {});
|
||||||
listResource("personen", "?rol=leerling").then(setLeerlingen).catch(() => {});
|
listResource("personen", "?rol=leerling").then(setLeerlingen).catch(() => {});
|
||||||
listResource("personen").then((ps) => setMedewerkers(ps.filter((p) => p.rol !== "leerling"))).catch(() => {});
|
listResource("personen").then((ps) => setMedewerkers(ps.filter((p) => p.rol !== "leerling"))).catch(() => {});
|
||||||
|
// Afdrukken alleen tonen als de print-module aanstaat.
|
||||||
|
getModules().then((ms) => setPrintAan(ms.some((m) => m.key === "printing" && m.enabled))).catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const weekDatums = [0, 1, 2, 3, 4].map((i) => plusDagen(maandag, i));
|
const weekDatums = [0, 1, 2, 3, 4].map((i) => plusDagen(maandag, i));
|
||||||
|
|
@ -1140,12 +1146,16 @@ export function RoosterPage() {
|
||||||
const persoonAfwezig = (id, iso) => afwezig.some((a) => a.persoon === id && a.start_datum <= iso && a.eind_datum >= iso);
|
const persoonAfwezig = (id, iso) => afwezig.some((a) => a.persoon === id && a.start_datum <= iso && a.eind_datum >= iso);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="rooster-scherm">
|
||||||
<h1>Rooster</h1>
|
<h1>{edit ? "Roostermaker" : "Rooster"}</h1>
|
||||||
<p className="muted">Sleep een blok naar een ander tijdslot om het te verplaatsen. Vrije dagen, afwezigheid en uitzonderingen worden meegenomen.</p>
|
<p className="muted no-print">
|
||||||
|
{edit
|
||||||
|
? "Sleep een blok naar een ander tijdslot om het te verplaatsen. Vrije dagen, afwezigheid en uitzonderingen worden meegenomen."
|
||||||
|
: "Bekijk het weekrooster. Vrije dagen, afwezigheid en uitzonderingen zijn verwerkt."}
|
||||||
|
</p>
|
||||||
<Foutmelding msg={error} />
|
<Foutmelding msg={error} />
|
||||||
|
|
||||||
<div className="rooster-balk">
|
<div className="rooster-balk no-print">
|
||||||
<label className="veld">Schooljaar
|
<label className="veld">Schooljaar
|
||||||
<select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}>
|
<select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}>
|
||||||
<option value="">— kies —</option>
|
<option value="">— kies —</option>
|
||||||
|
|
@ -1160,18 +1170,23 @@ export function RoosterPage() {
|
||||||
<span className="week-label">week van {fmtDM(maandag)}</span>
|
<span className="week-label">week van {fmtDM(maandag)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{schooljaar && tab === "week" && <button className="btn primary" onClick={() => setToonForm((v) => !v)}>{toonForm ? "Sluiten" : "+ Blok"}</button>}
|
{edit && schooljaar && tab === "week" && <button className="btn primary" onClick={() => setToonForm((v) => !v)}>{toonForm ? "Sluiten" : "+ Blok"}</button>}
|
||||||
|
{printAan && schooljaar && (edit ? tab === "week" : true) && (
|
||||||
|
<button className="btn print-knop" title="Rooster afdrukken" onClick={() => window.print()}>🖨 Afdrukken</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="tabbar">
|
{edit && (
|
||||||
<button className={tab === "week" ? "tab on" : "tab"} onClick={() => setTab("week")}>Weekrooster</button>
|
<div className="tabbar no-print">
|
||||||
<button className={tab === "conflicten" ? "tab on" : "tab"} onClick={() => setTab("conflicten")}>Conflicten</button>
|
<button className={tab === "week" ? "tab on" : "tab"} onClick={() => setTab("week")}>Weekrooster</button>
|
||||||
<button className={tab === "schooljaar" ? "tab on" : "tab"} onClick={() => setTab("schooljaar")}>Schooljaar</button>
|
<button className={tab === "conflicten" ? "tab on" : "tab"} onClick={() => setTab("conflicten")}>Conflicten</button>
|
||||||
<button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button>
|
<button className={tab === "schooljaar" ? "tab on" : "tab"} onClick={() => setTab("schooljaar")}>Schooljaar</button>
|
||||||
</div>
|
<button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{schooljaar && tab === "week" && toonForm && (
|
{edit && schooljaar && tab === "week" && toonForm && (
|
||||||
<form className="form-card" onSubmit={opslaan}>
|
<form className="form-card no-print" onSubmit={opslaan}>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<select value={form.tijdslot} required onChange={(e) => setForm({ ...form, tijdslot: e.target.value })}>
|
<select value={form.tijdslot} required onChange={(e) => setForm({ ...form, tijdslot: e.target.value })}>
|
||||||
<option value="">— tijdslot —</option>
|
<option value="">— tijdslot —</option>
|
||||||
|
|
@ -1244,9 +1259,9 @@ export function RoosterPage() {
|
||||||
style={{ borderLeftColor: b.activiteit_kleur }}
|
style={{ borderLeftColor: b.activiteit_kleur }}
|
||||||
>
|
>
|
||||||
<div className="rb-top">
|
<div className="rb-top">
|
||||||
<span className="rb-greep" onPointerDown={(e) => startDrag(e, b)} title="Sleep naar ander tijdslot">⠿</span>
|
{edit && <span className="rb-greep" onPointerDown={(e) => startDrag(e, b)} title="Sleep naar ander tijdslot">⠿</span>}
|
||||||
<strong>{b.activiteit_naam}</strong>
|
<strong>{b.activiteit_naam}</strong>
|
||||||
<button className="link danger rb-x" onClick={() => verwijder(b.id)} title="Blok verwijderen">×</button>
|
{edit && <button className="link danger rb-x" onClick={() => verwijder(b.id)} title="Blok verwijderen">×</button>}
|
||||||
</div>
|
</div>
|
||||||
<div className="small">🎯 {b.doel_naam} <span className="muted">({b.doel_type})</span></div>
|
<div className="small">🎯 {b.doel_naam} <span className="muted">({b.doel_type})</span></div>
|
||||||
{b.locatie_naam && <div className="small">📍 {b.locatie_naam}</div>}
|
{b.locatie_naam && <div className="small">📍 {b.locatie_naam}</div>}
|
||||||
|
|
@ -1256,10 +1271,10 @@ export function RoosterPage() {
|
||||||
))}</div>
|
))}</div>
|
||||||
)}
|
)}
|
||||||
{kal && <div className="small rb-status">Vervalt — {kal.type_label}</div>}
|
{kal && <div className="small rb-status">Vervalt — {kal.type_label}</div>}
|
||||||
{!kal && uit && uit.vervalt && <div className="small rb-status">Vervalt {uit.opmerking ? `(${uit.opmerking})` : ""} · <button className="link" onClick={() => herstel(uit.id)}>herstel</button></div>}
|
{!kal && uit && uit.vervalt && <div className="small rb-status">Vervalt {uit.opmerking ? `(${uit.opmerking})` : ""}{edit && <> · <button className="link" onClick={() => herstel(uit.id)}>herstel</button></>}</div>}
|
||||||
{!kal && uit && !uit.vervalt && uit.vervanger_naam && <div className="small rb-verv">↪ Vervanger: {uit.vervanger_naam}</div>}
|
{!kal && uit && !uit.vervalt && uit.vervanger_naam && <div className="small rb-verv">↪ Vervanger: {uit.vervanger_naam}</div>}
|
||||||
{!kal && !uit && (
|
{edit && !kal && !uit && (
|
||||||
<button className="link danger small rb-verval" onClick={() => laatVervallen(b, iso)}>deze dag laten vervallen</button>
|
<button className="link danger small rb-verval no-print" onClick={() => laatVervallen(b, iso)}>deze dag laten vervallen</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -1291,7 +1306,11 @@ export function RoosterPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ghost && <div className="drag-ghost" style={{ left: ghost.x + 10, top: ghost.y + 10 }}>{ghost.label}</div>}
|
{edit && ghost && <div className="drag-ghost" style={{ left: ghost.x + 10, top: ghost.y + 10 }}>{ghost.label}</div>}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Twee schermen op basis van dezelfde component.
|
||||||
|
export function RoosterWeergavePage() { return <RoosterScherm mode="view" />; }
|
||||||
|
export function RoostermakerPage() { return <RoosterScherm mode="edit" />; }
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,20 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
||||||
|
|
||||||
.drag-ghost { position: fixed; z-index: 1000; pointer-events: none; background: var(--brand); color: #fff; padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; box-shadow: 0 6px 18px rgba(0,0,0,.25); }
|
.drag-ghost { position: fixed; z-index: 1000; pointer-events: none; background: var(--brand); color: #fff; padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; box-shadow: 0 6px 18px rgba(0,0,0,.25); }
|
||||||
|
|
||||||
|
/* --- Afdrukken --- */
|
||||||
|
.print-knop { margin-left: auto; background: #fff; }
|
||||||
|
@media print {
|
||||||
|
.sidebar, .no-print, .tabbar, .rooster-balk, .banner,
|
||||||
|
.rb-greep, .rb-x, .rb-verval, .print-knop, .drag-ghost { display: none !important; }
|
||||||
|
body { background: #fff; }
|
||||||
|
.app { display: block; }
|
||||||
|
.content { padding: 0; }
|
||||||
|
.rooster-scherm h1 { margin: 0 0 8px; }
|
||||||
|
.rooster-grid { grid-template-columns: repeat(5, 1fr); gap: 4px; }
|
||||||
|
.rooster-kolom { border-color: #999; }
|
||||||
|
.rooster-blok { break-inside: avoid; }
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Onderkant zijbalk: subtiel Instellingen-blok --- */
|
/* --- Onderkant zijbalk: subtiel Instellingen-blok --- */
|
||||||
.sidebar-foot { margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line); }
|
.sidebar-foot { margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line); }
|
||||||
.nav-group-head.subtiel { color: var(--muted); opacity: .8; }
|
.nav-group-head.subtiel { color: var(--muted); opacity: .8; }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue